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

This commit is contained in:
Sam Gateau 2018-07-11 11:57:42 +02:00
commit 1b56653228
143 changed files with 7944 additions and 772 deletions

View file

@ -39,7 +39,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
### Step 6. Installing OpenSSL via vcpkg
* In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows`
* In the vcpkg directory, install the 64 bit OpenSSL package with the command `.\vcpkg install openssl:x64-windows`
* Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
### Step 7. Running CMake to Generate Build Files

View file

@ -1,8 +1,8 @@
High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR.
In this repository you'll find the source to many of the components in our
alpha-stage virtual world. The project embraces distributed development
and if you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net).
This repository contains the source to many of the components in our
alpha-stage virtual world. The project embraces distributed development.
If you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net).
If you find a small bug and have a fix, pull requests are welcome. If you'd
like to get paid for your work, make sure you report the bug via a job on
[Worklist.net](https://worklist.net).
@ -32,9 +32,10 @@ Running Interface
When you launch interface, you will automatically connect to our default domain: "root.highfidelity.io".
If you don't see anything, make sure your preferences are pointing to
root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D), if you still have no luck it's possible our servers are
simply down; if you're experiencing a major bug, let us know by adding an issue to this repository.
Make sure to include details about your computer and how to reproduce the bug.
root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D). If you still have no luck,
it's possible our servers are down. If you're experiencing a major bug, let us know by
adding an issue to this repository. Include details about your computer and how to
reproduce the bug in your issue.
To move around in-world, use the arrow keys (and Shift + up/down to fly up or
down) or W A S D, and E or C to fly up/down. All of the other possible options
@ -48,7 +49,8 @@ you to run the full stack of the virtual world.
In order to set up your own virtual world, you need to set up and run your own
local "domain".
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models.
The domain-server gives a number different types of assignments to the assignment-client
for different features: audio, avatars, voxels, particles, meta-voxels and models.
Follow the instructions in the [build guide](BUILD.md) to build the various components.
@ -56,7 +58,8 @@ From the domain-server build directory, launch a domain-server.
./domain-server
Then, run an assignment-client. The assignment-client uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port).
Then, run an assignment-client. The assignment-client uses localhost as its assignment-server
and talks to it on port 40102 (the default domain-server port).
In a new Terminal window, run:
@ -64,13 +67,20 @@ In a new Terminal window, run:
Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal window.
This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min` and `-max` options allow you to set a range of required assignment-clients, this allows you to have flexibility in the number of assignment-clients that are running. See `--help` for more options.
This assignment-client will grab one assignment from the domain-server. You can tell the
assignment-client what type you want it to be with the `-t` option. You can also run an
assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min`
and `-max` options allow you to set a range of required assignment-clients. This allows
you to have flexibility in the number of assignment-clients that are running.
See `--help` for more options.
./assignment-client --min 6 --max 20
To test things out you'll want to run the Interface client.
To test things out, you'll need to run the Interface client.
To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field.
To access your local domain in Interface, open your Preferences. On OS X, this is available
in the Interface menu. On Linux, you'll find it in the File menu. Enter "localhost" in the
"Domain server" field.
If everything worked you should see that you are connected to at least one server.
If everything worked, you should see that you are connected to at least one server.
Nice work!

View file

@ -29,6 +29,7 @@
</intent-filter>
</activity>
<activity android:name="io.highfidelity.hifiinterface.WebViewActivity"
android:configChanges="orientation|screenSize"
android:theme="@android:style/Theme.Material.Light.NoActionBar"/>
<!-- We don't want to show this on Daydream yet (we need to fix the turn-around problem on this screen)
<activity android:name="io.highfidelity.hifiinterface.GvrLoaderActivity">

View file

@ -153,10 +153,29 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
unpackAndroidAssets();
qInstallMessageHandler(oldMessageHandler);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a, const bool backToScene) {
JavaVM* jvm;
env->GetJavaVM(&jvm);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [jvm](const QString& a, const bool backToScene, QList<QString> args) {
JNIEnv* myNewEnv;
JavaVMAttachArgs jvmArgs;
jvmArgs.version = JNI_VERSION_1_6; // choose your JNI version
jvmArgs.name = NULL; // you might want to give the java thread a name
jvmArgs.group = NULL; // you might want to assign the java thread to a ThreadGroup
jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
QAndroidJniObject string = QAndroidJniObject::fromString(a);
jboolean jBackToScene = (jboolean) backToScene;
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;Z)V", string.object<jstring>(), jBackToScene);
jclass hashMapClass = myNewEnv->FindClass("java/util/HashMap");
jmethodID mapClassConstructor = myNewEnv->GetMethodID(hashMapClass, "<init>", "()V");
jobject hashmap = myNewEnv->NewObject(hashMapClass, mapClassConstructor);
jmethodID mapClassPut = myNewEnv->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
for (const QString& arg: args) {
QAndroidJniObject jArg = QAndroidJniObject::fromString(arg);
myNewEnv->CallObjectMethod(hashmap, mapClassPut, QAndroidJniObject::fromString("url").object<jstring>(), jArg.object<jstring>());
}
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;ZLjava/util/HashMap;)V", string.object<jstring>(), jBackToScene, hashmap);
jvm->DetachCurrentThread();
});
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) {
@ -295,4 +314,10 @@ Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEn
AndroidHelper::instance().notifyEnterForeground();
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessURL(JNIEnv* env, jobject obj, jstring url_str) {
const char *nativeString = env->GetStringUTFChars(url_str, 0);
AndroidHelper::instance().processURL(QString::fromUtf8(nativeString));
}
}

View file

@ -11,38 +11,48 @@
package io.highfidelity.hifiinterface;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.WindowManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.SlidingDrawer;
import org.qtproject.qt5.android.QtLayout;
import org.qtproject.qt5.android.QtSurface;
import org.qtproject.qt5.android.bindings.QtActivity;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import io.highfidelity.hifiinterface.fragment.WebViewFragment;
/*import com.google.vr.cardboard.DisplaySynchronizer;
import com.google.vr.cardboard.DisplayUtils;
import com.google.vr.ndk.base.GvrApi;*/
import android.graphics.Point;
import android.content.res.Configuration;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.view.View;
import android.widget.FrameLayout;
import java.lang.reflect.Field;
public class InterfaceActivity extends QtActivity {
public class InterfaceActivity extends QtActivity implements WebViewFragment.OnWebViewInteractionListener {
public static final String DOMAIN_URL = "url";
private static final String TAG = "Interface";
private static final int WEB_DRAWER_RIGHT_MARGIN = 262;
private static final int WEB_DRAWER_BOTTOM_MARGIN = 150;
private static final int NORMAL_DPI = 160;
private Vibrator mVibrator;
//public static native void handleHifiURL(String hifiURLString);
@ -58,6 +68,7 @@ public class InterfaceActivity extends QtActivity {
private static boolean inVrMode;
private boolean nativeEnterBackgroundCallEnqueued = false;
private SlidingDrawer webSlidingDrawer;
// private GvrApi gvrApi;
// Opaque native pointer to the Application C++ object.
// This object is owned by the InterfaceActivity instance and passed to the native methods.
@ -118,6 +129,25 @@ public class InterfaceActivity extends QtActivity {
});
startActivity(new Intent(this, SplashActivity.class));
mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
FrameLayout mainLayout = findViewById(android.R.id.content);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
webSlidingDrawer = (SlidingDrawer) inflater.inflate(R.layout.web_drawer, mainLayout, false);
QtLayout qtLayout = (QtLayout) mainLayout.getChildAt(0);
QtLayout.LayoutParams layoutParams = new QtLayout.LayoutParams(webSlidingDrawer.getLayoutParams());
webSlidingDrawer.setOnDrawerCloseListener(() -> {
WebViewFragment webViewFragment = (WebViewFragment) getFragmentManager().findFragmentByTag("webViewFragment");
webViewFragment.close();
});
int widthPx = Math.max(size.x, size.y);
int heightPx = Math.min(size.x, size.y);
layoutParams.x = (int) (widthPx - WEB_DRAWER_RIGHT_MARGIN * getResources().getDisplayMetrics().xdpi / NORMAL_DPI);
layoutParams.y = (int) (heightPx - WEB_DRAWER_BOTTOM_MARGIN * getResources().getDisplayMetrics().ydpi / NORMAL_DPI);
layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL);
qtLayout.addView(webSlidingDrawer, layoutParams);
webSlidingDrawer.setVisibility(View.GONE);
}
@Override
@ -149,6 +179,7 @@ public class InterfaceActivity extends QtActivity {
super.onResume();
nativeEnterForeground();
surfacesWorkaround();
keepInterfaceRunning = false;
//gvrApi.resumeTracking();
}
@ -180,9 +211,16 @@ public class InterfaceActivity extends QtActivity {
FrameLayout fl = findViewById(android.R.id.content);
if (fl.getChildCount() > 0) {
QtLayout qtLayout = (QtLayout) fl.getChildAt(0);
if (qtLayout.getChildCount() > 1) {
QtSurface s1 = (QtSurface) qtLayout.getChildAt(0);
QtSurface s2 = (QtSurface) qtLayout.getChildAt(1);
List<QtSurface> surfaces = new ArrayList<>();
for (int i = 0; i < qtLayout.getChildCount(); i++) {
Object ch = qtLayout.getChildAt(i);
if (ch instanceof QtSurface) {
surfaces.add((QtSurface) ch);
}
}
if (surfaces.size() > 1) {
QtSurface s1 = surfaces.get(0);
QtSurface s2 = surfaces.get(1);
Integer subLayer1 = 0;
Integer subLayer2 = 0;
try {
@ -239,11 +277,16 @@ public class InterfaceActivity extends QtActivity {
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.hasExtra(DOMAIN_URL)) {
webSlidingDrawer.setVisibility(View.GONE);
nativeGotoUrl(intent.getStringExtra(DOMAIN_URL));
}
}
public void openAndroidActivity(String activityName, boolean backToScene) {
openAndroidActivity(activityName, backToScene, null);
}
public void openAndroidActivity(String activityName, boolean backToScene, HashMap args) {
switch (activityName) {
case "Home":
case "Privacy Policy":
@ -254,6 +297,25 @@ public class InterfaceActivity extends QtActivity {
startActivity(intent);
break;
}
case "WebView":
runOnUiThread(() -> {
webSlidingDrawer.setVisibility(View.VISIBLE);
if (!webSlidingDrawer.isOpened()) {
webSlidingDrawer.animateOpen();
}
if (args != null && args.containsKey(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL)) {
WebViewFragment webViewFragment = (WebViewFragment) getFragmentManager().findFragmentByTag("webViewFragment");
webViewFragment.loadUrl((String) args.get(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL), true);
webViewFragment.setToolbarVisible(true);
webViewFragment.setCloseAction(() -> {
if (webSlidingDrawer.isOpened()) {
webSlidingDrawer.animateClose();
}
webSlidingDrawer.setVisibility(View.GONE);
});
}
});
break;
default: {
Log.w(TAG, "Could not open activity by name " + activityName);
break;
@ -278,4 +340,18 @@ public class InterfaceActivity extends QtActivity {
public void onBackPressed() {
openAndroidActivity("Home", false);
}
@Override
public void processURL(String url) { }
@Override
public void onWebLoaded(String url, WebViewFragment.SafenessLevel safenessLevel) { }
@Override
public void onTitleReceived(String title) { }
@Override
public void onExpand() {
keepInterfaceRunning = true;
}
}

View file

@ -8,93 +8,64 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//package hifiinterface.highfidelity.io.mybrowserapplication;
package io.highfidelity.hifiinterface;
import android.app.ActionBar;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import android.widget.Toolbar;
import android.os.Looper;
import java.lang.Thread;
import java.lang.Runnable;
import java.net.MalformedURLException;
import java.net.URL;
public class WebViewActivity extends Activity {
import io.highfidelity.hifiinterface.fragment.WebViewFragment;
public class WebViewActivity extends Activity implements WebViewFragment.OnWebViewInteractionListener {
public static final String WEB_VIEW_ACTIVITY_EXTRA_URL = "url";
private static final String FRAGMENT_TAG = "WebViewActivity_WebFragment";
private native void nativeProcessURL(String url);
private WebView myWebView;
private ProgressBar mProgressBar;
private ActionBar mActionBar;
private String mUrl;
enum SafenessLevel {
NOT_ANALYZED_YET(""),
NOT_SECURE(""),
SECURE("\uD83D\uDD12 "),
BAD_SECURE("\uD83D\uDD13 ");
String icon;
SafenessLevel(String icon) {
this.icon = icon;
}
}
private SafenessLevel safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view);
setActionBar((Toolbar) findViewById(R.id.toolbar_actionbar));
setActionBar(findViewById(R.id.toolbar_actionbar));
mActionBar = getActionBar();
mActionBar.setDisplayHomeAsUpEnabled(true);
mProgressBar = (ProgressBar) findViewById(R.id.toolbarProgressBar);
loadWebViewFragment(getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL));
}
mUrl = getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL);
myWebView = (WebView) findViewById(R.id.web_view);
myWebView.setWebViewClient(new HiFiWebViewClient());
myWebView.setWebChromeClient(new HiFiWebChromeClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setDisplayZoomControls(false);
myWebView.loadUrl(mUrl);
private void loadWebViewFragment(String url) {
WebViewFragment fragment = WebViewFragment.newInstance();
Bundle bundle = new Bundle();
bundle.putString(WebViewFragment.URL, url);
bundle.putBoolean(WebViewFragment.TOOLBAR_VISIBLE, false);
fragment.setArguments(bundle);
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment, FRAGMENT_TAG);
ft.commit();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
WebViewFragment fragment = (WebViewFragment) getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
if (fragment != null && fragment.onKeyDown(keyCode)) {
return true;
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
@ -102,15 +73,6 @@ public class WebViewActivity extends Activity {
return super.onKeyDown(keyCode, event);
}
private void showSubtitleWithUrl(String url) {
try {
mActionBar.setSubtitle(safenessLevel.icon + new URL(url.toString()).getHost());
} catch (MalformedURLException e) {
Toast.makeText(WebViewActivity.this, "Error loading page: " + "bad url", Toast.LENGTH_LONG).show();
Log.e("openUrl", "bad url");
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
@ -119,7 +81,7 @@ public class WebViewActivity extends Activity {
}
private String intentUrlOrWebUrl() {
return myWebView==null || myWebView.getUrl()==null?mUrl:myWebView.getUrl();
return ((WebViewFragment) getFragmentManager().findFragmentById(R.id.content_frame)).intentUrlOrWebUrl();
}
@Override
@ -145,98 +107,28 @@ public class WebViewActivity extends Activity {
return super.onOptionsItemSelected(item);
}
class HiFiWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressBar.setVisibility(View.GONE);
if (safenessLevel!=SafenessLevel.BAD_SECURE) {
if (url.startsWith("https:")) {
safenessLevel=SafenessLevel.SECURE;
} else {
safenessLevel=SafenessLevel.NOT_SECURE;
}
}
showSubtitleWithUrl(url);
}
@Override
public void processURL(String url) {
nativeProcessURL(url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setProgress(0);
showSubtitleWithUrl(url);
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
Toast.makeText(WebViewActivity.this, "Error loading page: " + error.getDescription(), Toast.LENGTH_LONG).show();
if (ERROR_FAILED_SSL_HANDSHAKE == error.getErrorCode()) {
safenessLevel = SafenessLevel.BAD_SECURE;
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
Toast.makeText(WebViewActivity.this, "Network Error loading page: " + errorResponse.getReasonPhrase(), Toast.LENGTH_LONG).show();
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Toast.makeText(WebViewActivity.this, "SSL error loading page: " + error.toString(), Toast.LENGTH_LONG).show();
safenessLevel = SafenessLevel.BAD_SECURE;
}
private boolean isFst(WebResourceRequest request) {
return isFst(request.getUrl().toString());
}
private boolean isFst(String url) {
return url.endsWith(".fst");
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// managing avatar selections
if (isFst(request)) {
final String url = request.getUrl().toString();
new Thread(new Runnable() {
public void run() {
nativeProcessURL(url);
}
}).start(); // Avoid deadlock in Qt dialog
WebViewActivity.this.finish();
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public void onLoadResource(WebView view, String url) {
if (isFst(url)) {
// processed separately
} else {
super.onLoadResource(view, url);
}
@Override
public void onWebLoaded(String url, WebViewFragment.SafenessLevel safenessLevel) {
try {
mActionBar.setSubtitle(safenessLevel.icon + new URL(url.toString()).getHost());
} catch (MalformedURLException e) {
Toast.makeText(WebViewActivity.this, "Error loading page: " + "bad url", Toast.LENGTH_LONG).show();
Log.e("openUrl", "bad url");
}
}
class HiFiWebChromeClient extends WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mProgressBar.setProgress(newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
mActionBar.setTitle(title);
}
@Override
public void onTitleReceived(String title) {
mActionBar.setTitle(title);
}
@Override
public void onExpand() { }
}

View file

@ -0,0 +1,343 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.WebViewActivity;
public class WebViewFragment extends Fragment implements GestureDetector.OnGestureListener {
public static final String URL = "url";
public static final String TOOLBAR_VISIBLE = "toolbar_visible";
private static final long DELAY_HIDE_TOOLBAR_MILLIS = 3000;
private static final long FADE_OUT_DURATION = 2000;
private WebView myWebView;
private GestureDetector gestureDetector;
private View mToolbar;
private ProgressBar mProgressBar;
private String mUrl;
private boolean mToolbarVisible;
private OnWebViewInteractionListener mListener;
private Runnable mCloseAction;
private Handler mHandler;
private Runnable mHideToolbar = new Runnable() {
@Override
public void run() {
if (mToolbar != null) {
AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
anim.setDuration(FADE_OUT_DURATION);
anim.setFillAfter(true);
mToolbar.startAnimation(anim);
}
}
};
public boolean onKeyDown(int keyCode) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
return false;
}
public String intentUrlOrWebUrl() {
return myWebView == null || myWebView.getUrl() == null ? mUrl : myWebView.getUrl();
}
public void loadUrl(String url, boolean showToolbar) {
mUrl = url;
mToolbarVisible = showToolbar;
loadUrl(myWebView, url);
}
private void loadUrl(WebView webView, String url) {
webView.setVisibility(View.GONE);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setUseWideViewPort(true);
webView.loadUrl(url);
mToolbar.setVisibility(mToolbarVisible ? View.VISIBLE : View.GONE);
mToolbar.clearAnimation();
if (mToolbarVisible) {
mHandler.postDelayed(mHideToolbar, DELAY_HIDE_TOOLBAR_MILLIS);
}
}
public void setToolbarVisible(boolean visible) {
mToolbar.setVisibility(visible ? View.VISIBLE : View.GONE);
}
public void setCloseAction(Runnable closeAction) {
this.mCloseAction = closeAction;
}
@Override
public boolean onDown(MotionEvent motionEvent) {
mHandler.removeCallbacks(mHideToolbar);
if (mToolbarVisible) {
mToolbar.setVisibility(mToolbarVisible ? View.VISIBLE : View.GONE);
mToolbar.clearAnimation();
mHandler.postDelayed(mHideToolbar, DELAY_HIDE_TOOLBAR_MILLIS);
}
return false;
}
@Override
public void onShowPress(MotionEvent motionEvent) {
}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
return false;
}
@Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
return false;
}
@Override
public void onLongPress(MotionEvent motionEvent) {
}
@Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
return false;
}
public void close() {
myWebView.loadUrl("about:blank");
if (mCloseAction != null) {
mCloseAction.run();
}
}
public enum SafenessLevel {
NOT_ANALYZED_YET(""),
NOT_SECURE(""),
SECURE("\uD83D\uDD12 "),
BAD_SECURE("\uD83D\uDD13 ");
public String icon;
SafenessLevel(String icon) {
this.icon = icon;
}
}
private SafenessLevel safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
public WebViewFragment() {
// Required empty public constructor
}
public static WebViewFragment newInstance() {
WebViewFragment fragment = new WebViewFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mUrl = getArguments().getString(URL);
mToolbarVisible = getArguments().getBoolean(TOOLBAR_VISIBLE);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_web_view, container, false);
mProgressBar = rootView.findViewById(R.id.toolbarProgressBar);
myWebView = rootView.findViewById(R.id.web_view);
mHandler = new Handler();
gestureDetector = new GestureDetector(this);
gestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
return false;
}
@Override
public boolean onDoubleTap(MotionEvent motionEvent) {
expand();
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent motionEvent) {
return false;
}
});
myWebView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
myWebView.setWebViewClient(new HiFiWebViewClient());
myWebView.setWebChromeClient(new HiFiWebChromeClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setDisplayZoomControls(false);
mToolbar = rootView.findViewById(R.id.toolbar);
mToolbar.findViewById(R.id.viewFullScreen).setOnClickListener(view -> {
expand();
});
mToolbar.findViewById(R.id.close).setOnClickListener(view -> {
close();
});
if (mUrl != null) {
loadUrl(myWebView, mUrl);
}
return rootView;
}
private void expand() {
if (mListener != null) {
mListener.onExpand();
}
Intent intent = new Intent(getActivity(), WebViewActivity.class);
intent.putExtra(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL, intentUrlOrWebUrl());
getActivity().startActivity(intent);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnWebViewInteractionListener) {
mListener = (OnWebViewInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnWebViewInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnWebViewInteractionListener {
void processURL(String url);
void onWebLoaded(String url, SafenessLevel safenessLevel);
void onTitleReceived(String title);
void onExpand();
}
class HiFiWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressBar.setVisibility(View.GONE);
if (safenessLevel!= SafenessLevel.BAD_SECURE) {
if (url.startsWith("https:")) {
safenessLevel = SafenessLevel.SECURE;
} else {
safenessLevel = SafenessLevel.NOT_SECURE;
}
}
if (mListener != null) {
myWebView.setVisibility(View.VISIBLE);
mListener.onWebLoaded(url, safenessLevel);
}
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setProgress(0);
if (mListener != null) {
myWebView.setVisibility(View.VISIBLE);
mListener.onWebLoaded(url, safenessLevel);
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
Toast.makeText(getActivity(), "Error loading page: " + error.getDescription(), Toast.LENGTH_LONG).show();
if (ERROR_FAILED_SSL_HANDSHAKE == error.getErrorCode()) {
safenessLevel = SafenessLevel.BAD_SECURE;
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
Toast.makeText(getActivity(), "Network Error loading page: " + errorResponse.getReasonPhrase(), Toast.LENGTH_LONG).show();
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Toast.makeText(getActivity(), "SSL error loading page: " + error.toString(), Toast.LENGTH_LONG).show();
safenessLevel = SafenessLevel.BAD_SECURE;
}
private boolean isFst(WebResourceRequest request) {
return isFst(request.getUrl().toString());
}
private boolean isFst(String url) {
return url.contains(".fst");
}
@Override
public void onLoadResource(WebView view, String url) {
if (isFst(url)) {
// processed separately
} else {
super.onLoadResource(view, url);
}
}
}
class HiFiWebChromeClient extends WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mProgressBar.setProgress(newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
if (mListener != null) {
mListener.onTitleReceived(title);
}
}
}
}

View file

@ -69,6 +69,7 @@ public class QtActivity extends Activity {
private QtActivityLoader m_loader = new QtActivityLoader(this);
public boolean isLoading;
public boolean keepInterfaceRunning;
public QtActivity() {
}
@ -503,7 +504,7 @@ public class QtActivity extends Activity {
super.onPause();
// GC: this trick allow us to show a splash activity until Qt app finishes
// loading
if (!isLoading) {
if (!isLoading && !keepInterfaceRunning) {
QtApplication.invokeDelegate();
}
}
@ -644,7 +645,9 @@ public class QtActivity extends Activity {
@Override
protected void onStop() {
super.onStop();
QtApplication.invokeDelegate();
if (!keepInterfaceRunning) {
QtApplication.invokeDelegate();
}
}
//---------------------------------------------------------------------------

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="173dp"
android:height="173dp"
android:viewportWidth="173"
android:viewportHeight="173">
<path
android:name="path"
android:pathData="M 86.5 173 C 134.273 173 173 134.273 173 86.5 C 173 38.727 134.273 0 86.5 0 C 38.727 0 0 38.727 0 86.5 C 0 134.273 38.727 173 86.5 173 Z"
android:fillColor="#181818"
android:fillAlpha="0.6"
android:strokeWidth="1"/>
<path
android:name="path_1"
android:pathData="M 53.3 53.3 L 119.7 119.7 M 53.3 119.7 L 119.7 53.3"
android:fillColor="#000"
android:fillAlpha="0.6"
android:strokeColor="#ffffff"
android:strokeWidth="13.5424"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="173dp"
android:height="173dp"
android:viewportWidth="173"
android:viewportHeight="173">
<path
android:name="path"
android:pathData="M 86.5 173 C 134.273 173 173 134.273 173 86.5 C 173 38.727 134.273 0 86.5 0 C 38.727 0 0 38.727 0 86.5 C 0 134.273 38.727 173 86.5 173 Z"
android:fillColor="#181818"
android:fillAlpha="0.6"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 52.4 52.4 C 52.4 58.4 52.3 63.7 52.4 69 C 52.5 71.8 51.5 72.7 48.7 72.7 C 37.6 72.4 39.7 74.1 39.6 63.6 C 39.5 56.9 39.7 50.1 39.5 43.4 C 39.4 40.4 40.5 39.5 43.4 39.6 C 51.8 39.7 60.2 39.7 68.7 39.6 C 71.6 39.6 72.7 40.4 72.6 43.4 C 72.3 54.2 73.9 52.2 63.8 52.4 L 52.4 52.4 Z M 120.6 52.4 C 114.8 52.4 109.5 52.3 104.2 52.5 C 101.3 52.6 100.2 51.6 100.3 48.7 C 100.6 38 99 39.9 109.1 39.7 C 116 39.6 122.9 39.8 129.7 39.6 C 132.5 39.5 133.4 40.5 133.4 43.3 C 133.3 52 133.3 60.7 133.4 69.4 C 133.4 71.7 132.7 72.8 130.3 72.7 C 118.4 72.5 120.9 74.1 120.6 63.7 L 120.6 52.4 Z M 52.4 120.6 C 58.4 120.6 63.7 120.7 69 120.6 C 71.7 120.5 72.8 121.4 72.7 124.2 C 72.4 135.4 74 133.2 63.7 133.4 C 56.8 133.5 49.9 133.3 43.1 133.5 C 40.6 133.5 39.6 132.7 39.6 130.1 C 39.7 121.4 39.7 112.7 39.6 104 C 39.6 101.6 40.2 100.4 42.9 100.4 C 54.5 100.6 52.2 99 52.4 109.5 C 52.5 113 52.4 116.4 52.4 120.6 Z M 120.6 120.6 C 120.6 114.6 120.7 109.4 120.6 104.2 C 120.5 101.3 121.4 100.2 124.4 100.2 C 135.2 100.5 133.3 98.9 133.4 109 C 133.5 115.9 133.3 122.8 133.5 129.6 C 133.6 132.3 132.7 133.3 129.9 133.3 C 121.2 133.2 112.5 133.2 103.8 133.3 C 101.2 133.3 100.4 132.3 100.4 129.8 C 100.6 118.5 99.1 120.7 109.3 120.5 C 112.9 120.6 116.5 120.6 120.6 120.6 Z"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
</vector>

View file

@ -1,8 +1,9 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<Toolbar
android:id="@+id/toolbar_actionbar"
android:layout_width="match_parent"
@ -16,19 +17,9 @@
android:contentInsetStartWithNavigation="0dp"
android:title="">
</Toolbar>
<WebView
android:id="@+id/web_view"
<FrameLayout
android:id="@+id/content_frame"
android:layout_below="@id/toolbar_actionbar"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/toolbarProgressBar"
android:layout_below="@id/toolbar_actionbar"
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="3dp"
android:indeterminate="false"
android:padding="0dp" />
android:layout_height="match_parent" />
</RelativeLayout>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.constraint.ConstraintLayout
android:id="@+id/toolbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_marginTop="6dp"
android:layout_marginRight="5dp"
android:visibility="gone">
<ImageView
android:id="@+id/viewFullScreen"
android:layout_width="31dp"
android:layout_height="31dp"
android:src="@drawable/ic_expand" />
<ImageView
android:id="@+id/close"
android:layout_width="31dp"
android:layout_height="31dp"
app:layout_constraintLeft_toRightOf="@id/viewFullScreen"
android:layout_marginLeft="5dp"
android:src="@drawable/ic_close" />
</android.support.constraint.ConstraintLayout>
<ProgressBar
android:id="@+id/toolbarProgressBar"
android:layout_below="@id/toolbar_actionbar"
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="3dp"
android:indeterminate="false"
android:padding="0dp" />
</RelativeLayout>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<SlidingDrawer android:id="@+id/drawer"
android:layout_width="218dp"
android:layout_height="125dp"
android:layout_gravity="bottom|right"
android:layout_marginBottom="11dp"
android:layout_marginRight="11dp"
android:handle="@+id/handle"
android:content="@+id/content"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@id/handle"
android:layout_width="0dp"
android:layout_height="0dp"/>
<fragment
android:id="@id/content"
android:name="io.highfidelity.hifiinterface.fragment.WebViewFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="webViewFragment"
/>
</SlidingDrawer>

View file

@ -53,6 +53,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket");
packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity,
@ -602,6 +603,31 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
_handleAvatarIdentityPacketElapsedTime += (end - start);
}
void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
if (message->getSize() < NUM_BYTES_RFC4122_UUID) {
qCDebug(avatars) << "Malformed AvatarIdentityRequest received from" << message->getSenderSockAddr().toString();
return;
}
QUuid avatarID(QUuid::fromRfc4122(message->getMessage()) );
if (!avatarID.isNull()) {
auto nodeList = DependencyManager::get<NodeList>();
auto node = nodeList->nodeWithUUID(avatarID);
if (node) {
QMutexLocker lock(&node->getMutex());
AvatarMixerClientData* avatarClientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
if (avatarClientData) {
const AvatarData& avatarData = avatarClientData->getAvatar();
QByteArray serializedAvatar = avatarData.identityByteArray();
auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
identityPackets->write(serializedAvatar);
nodeList->sendPacketList(std::move(identityPackets), *senderNode);
++_sumIdentityPackets;
}
}
}
}
void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
auto start = usecTimestampNow();
handleAvatarKilled(node);

View file

@ -54,6 +54,7 @@ private slots:
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleReplicatedPacket(QSharedPointer<ReceivedMessage> message);
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
void handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
void start();

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68-v2.zip
URL_MD5 f7d290471baf7f5694c147217b8fc548
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC69.zip
URL_MD5 e2467b08de069da7e22ec8e032435592
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -566,7 +566,7 @@
},
{
"id": "idleToWalkFwd",
"interpTarget": 3,
"interpTarget": 10,
"interpDuration": 3,
"transitions": [
{ "var": "idleToWalkFwdOnDone", "state": "walkFwd" },
@ -603,8 +603,8 @@
},
{
"id": "walkBwd",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 8,
"interpDuration": 2,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
@ -621,8 +621,8 @@
},
{
"id": "strafeRight",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 20,
"interpDuration": 1,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
@ -639,8 +639,8 @@
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"interpTarget": 20,
"interpDuration": 1,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },

View file

@ -0,0 +1,777 @@
{
"Anime boy": {
"attachments": [
],
"avatarEntites": [
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 6.915350914001465,
"ageAsText": "0 hours 0 minutes 6 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.10961885005235672,
"y": -0.19444090127944946,
"z": -0.15760529041290283
},
"center": {
"x": 2.6226043701171875e-06,
"y": -0.13999652862548828,
"z": -0.04999971389770508
},
"dimensions": {
"x": 0.21924294531345367,
"y": 0.10888873785734177,
"z": 0.2152111530303955
},
"tfl": {
"x": 0.10962409526109695,
"y": -0.0855521634221077,
"z": 0.057605862617492676
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:27:53Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.21924294531345367,
"y": 0.07768379896879196,
"z": 0.2055898904800415
},
"dynamic": false,
"editionNumber": 15,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{5d20c775-a0d7-4163-b158-4e0a784a4625}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "Wear these, and others will respect your authoritah.",
"itemLicense": "",
"itemName": "Aviators",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306178314655,
"lastEditedBy": "{439a2669-4626-487f-9dcf-2d15e77c69a2}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": 2.6226043701171875e-06,
"y": -0.13999652862548828,
"z": -0.04999971389770508
},
"localRotation": {
"w": 0.9969173073768616,
"x": -0.07845909893512726,
"y": 0,
"z": 0
},
"locked": false,
"marketplaceID": "40d879ec-93f0-4b4a-8c58-dd6349bdb058",
"modelURL": "http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx",
"name": "",
"naturalDimensions": {
"x": 0.1660931408405304,
"y": 0.05885136127471924,
"z": 0.15574991703033447
},
"naturalPosition": {
"x": 0,
"y": 1.6633577346801758,
"z": 0.048884183168411255
},
"originalTextures": "{\n \"aviator:Eyewear2F\": \"http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx/Aviator.fbm/aviator_Eyewear_Diffuse.png\",\n \"aviator:Eyewear2F1\": \"http://mpassets.highfidelity.com/40d879ec-93f0-4b4a-8c58-dd6349bdb058-v1/Aviator.fbx/Aviator.fbm/aviator_Eyewear_Specular.png\"\n}\n",
"owningAvatarID": "{439a2669-4626-487f-9dcf-2d15e77c69a2}",
"parentID": "{439a2669-4626-487f-9dcf-2d15e77c69a2}",
"parentJointIndex": 66,
"position": {
"x": 2.6226043701171875e-06,
"y": -0.13999652862548828,
"z": -0.04999971389770508
},
"queryAACube": {
"scale": 0.9313028454780579,
"x": -1.4091639518737793,
"y": -10.133878707885742,
"z": 1.9983724355697632
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 2,
"texturesSize": 1310720,
"verticesCount": 982
},
"restitution": 0.5,
"rotation": {
"w": 0.9969173073768616,
"x": -0.07845909893512726,
"y": 0,
"z": 0
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
}
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst",
"version": 3
},
"Anime girl": {
"attachments": [
],
"avatarEntites": [
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 19.66267967224121,
"ageAsText": "0 hours 0 minutes 19 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.10536206513643265,
"y": -0.16647332906723022,
"z": -0.12632352113723755
},
"center": {
"x": 0,
"y": -0.12999999523162842,
"z": -0.030000001192092896
},
"dimensions": {
"x": 0.2107241302728653,
"y": 0.07294666767120361,
"z": 0.1926470398902893
},
"tfl": {
"x": 0.10536206513643265,
"y": -0.09352666139602661,
"z": 0.06632351875305176
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-05T00:10:37Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.2107241302728653,
"y": 0.07294666767120361,
"z": 0.1926470398902893
},
"dynamic": false,
"editionNumber": 5,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{1586b83a-2af7-4532-9bfb-82fe3f5d5ce9}",
"ignoreForCollisions": false,
"itemArtist": "moam_00",
"itemCategories": "Wearables",
"itemDescription": "Perfect for side-glancin'.",
"itemLicense": "",
"itemName": "Blacker Fem Glasses",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528157470041658,
"lastEditedBy": "{425df1a8-289b-42fc-819c-c3b2a12d7165}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": 0,
"y": -0.12999999523162842,
"z": -0.029999999329447746
},
"localRotation": {
"w": 1,
"x": -2.2351741790771484e-08,
"y": 3.4924596548080444e-10,
"z": 3.725290298461914e-09
},
"locked": false,
"marketplaceID": "06781d12-9139-48f4-ac2a-417dde090981",
"modelURL": "http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx",
"name": "Female Glasses 3 by Mario Andrade",
"naturalDimensions": {
"x": 0.16209548711776733,
"y": 0.05611282214522362,
"z": 0.14819003641605377
},
"naturalPosition": {
"x": 0,
"y": -7.636845111846924e-08,
"z": 0
},
"originalTextures": "{\n \"file49\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Mixed_AO.jpg\",\n \"file81\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Metallic.jpg\",\n \"file84\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Roughness.jpg\",\n \"file86\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Base_Color.jpg\",\n \"file87\": \"http://mpassets.highfidelity.com/06781d12-9139-48f4-ac2a-417dde090981-v1/FemGlasses03.fbx/FemGlasses03.fbm/FemGlasses03Mat_Normal_DirectX.jpg\"\n}\n",
"owningAvatarID": "{1277f725-fbb4-478b-ae79-1241fd90e508}",
"parentID": "{1277f725-fbb4-478b-ae79-1241fd90e508}",
"parentJointIndex": 66,
"position": {
"x": 0,
"y": -0.12999999523162842,
"z": -0.029999999329447746
},
"queryAACube": {
"scale": 0.8840523958206177,
"x": -2.6587564945220947,
"y": -10.162277221679688,
"z": -0.9548344016075134
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 5,
"texturesSize": 0,
"verticesCount": 1156
},
"restitution": 0.5,
"rotation": {
"w": 1,
"x": -2.2351741790771484e-08,
"y": 3.4924596548080444e-10,
"z": 3.725290298461914e-09
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
}
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst",
"version": 3
},
"Last Legends: Male": {
"attachments": [
],
"avatarEntites": [
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 14.011327743530273,
"ageAsText": "0 hours 0 minutes 14 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.20154684782028198,
"y": 0.03644842654466629,
"z": -0.2641940414905548
},
"center": {
"x": -0.030000001192092896,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"dimensions": {
"x": 0.3430936932563782,
"y": 0.18709957599639893,
"z": 0.38838762044906616
},
"tfl": {
"x": 0.1415468454360962,
"y": 0.22354799509048462,
"z": 0.12419357895851135
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:25:42Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.33466479182243347,
"y": 0.16981728374958038,
"z": 0.38838762044906616
},
"dynamic": false,
"editionNumber": 19,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{6b0a2b08-e8e3-4d43-95cc-dfc4f7a4b0c9}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "A stylish and classic piece of headwear for your avatar.",
"itemLicense": "",
"itemName": "Fedora",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306032827319,
"lastEditedBy": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": -0.030000008642673492,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"localRotation": {
"w": 0.9996573328971863,
"x": 0,
"y": 0,
"z": 0.026176949962973595
},
"locked": false,
"marketplaceID": "11c4208d-15d7-4449-9758-a08da6dbd3dc",
"modelURL": "http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx",
"name": "",
"naturalDimensions": {
"x": 0.2765824794769287,
"y": 0.14034485816955566,
"z": 0.320981502532959
},
"naturalPosition": {
"x": 0.000143393874168396,
"y": 1.7460365295410156,
"z": 0.022502630949020386
},
"originalTextures": "{\n \"file5\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Base_Color.png\",\n \"file7\": \"http://mpassets.highfidelity.com/11c4208d-15d7-4449-9758-a08da6dbd3dc-v1/Fedora.fbx/Texture/Fedora_Hat1_Roughness.png\"\n}\n",
"owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentJointIndex": 64,
"position": {
"x": -0.030000008642673492,
"y": 0.12999820709228516,
"z": -0.07000023126602173
},
"queryAACube": {
"scale": 1.6202316284179688,
"x": -0.5601736903190613,
"y": -10.668098449707031,
"z": -0.8933582305908203
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 2,
"texturesSize": 327680,
"verticesCount": 719
},
"restitution": 0.5,
"rotation": {
"w": 0.9996573328971863,
"x": 0,
"y": 0,
"z": 0.026176949962973595
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"HeadTop_End\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
},
{
"properties": {
"acceleration": {
"x": 0,
"y": 0,
"z": 0
},
"actionData": "",
"age": 14.011027336120605,
"ageAsText": "0 hours 0 minutes 14 seconds",
"angularDamping": 0.39346998929977417,
"angularVelocity": {
"x": 0,
"y": 0,
"z": 0
},
"animation": {
"allowTranslation": true,
"currentFrame": 0,
"firstFrame": 0,
"fps": 30,
"hold": false,
"lastFrame": 100000,
"loop": true,
"running": false,
"url": ""
},
"boundingBox": {
"brn": {
"x": -0.04381517320871353,
"y": 0.20789726078510284,
"z": -0.0394962802529335
},
"center": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"dimensions": {
"x": 0.08762653172016144,
"y": 0.04429228603839874,
"z": 0.07901687920093536
},
"tfl": {
"x": 0.043811358511447906,
"y": 0.2521895468235016,
"z": 0.03952059894800186
}
},
"canCastShadow": true,
"certificateID": "",
"clientOnly": true,
"cloneAvatarEntity": false,
"cloneDynamic": false,
"cloneLifetime": 300,
"cloneLimit": 0,
"cloneOriginID": "{00000000-0000-0000-0000-000000000000}",
"cloneable": false,
"collidesWith": "",
"collisionMask": 0,
"collisionSoundURL": "",
"collisionless": false,
"collisionsWillMove": false,
"compoundShapeURL": "",
"created": "2018-06-06T17:25:42Z",
"damping": 0.39346998929977417,
"density": 1000,
"description": "",
"dimensions": {
"x": 0.03022606298327446,
"y": 0.06644226610660553,
"z": 0.07229919731616974
},
"dynamic": false,
"editionNumber": 58,
"entityInstanceNumber": 0,
"friction": 0.5,
"gravity": {
"x": 0,
"y": 0,
"z": 0
},
"href": "",
"id": "{d018c6ea-b2f4-441e-85e1-d17373ae6f34}",
"ignoreForCollisions": false,
"itemArtist": "jyoum",
"itemCategories": "Wearables",
"itemDescription": "A cool scifi watch for your avatar!",
"itemLicense": "",
"itemName": "Scifi Watch",
"jointRotations": [
],
"jointRotationsSet": [
],
"jointTranslations": [
],
"jointTranslationsSet": [
],
"lastEdited": 1528306032505220,
"lastEditedBy": "{b46f9c9e-4cd3-4964-96d6-cf3954abb908}",
"lifetime": -1,
"limitedRun": 4294967295,
"localPosition": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"localRotation": {
"w": 0.5910987257957458,
"x": -0.48726412653923035,
"y": -0.4088631868362427,
"z": 0.49599069356918335
},
"locked": false,
"marketplaceID": "0685794d-fddb-4bad-a608-6d7789ceda90",
"modelURL": "http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx",
"name": "Scifi Watch by Jimi",
"naturalDimensions": {
"x": 0.023250818252563477,
"y": 0.0511094331741333,
"z": 0.055614765733480453
},
"naturalPosition": {
"x": 0.6493338942527771,
"y": 1.4500460624694824,
"z": -0.06031447649002075
},
"originalTextures": "{\n \"file4\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Base_Color.png\",\n \"file5\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Normal_OpenGL.png\",\n \"file6\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Metallic.png\",\n \"file7\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Roughness.png\",\n \"file8\": \"http://mpassets.highfidelity.com/0685794d-fddb-4bad-a608-6d7789ceda90-v1/ScifiWatch.fbx/ScifiWatch/texture/lambert1_Emissive.png\"\n}\n",
"owningAvatarID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentID": "{4c770def-4abb-40c6-91a1-88da5247b2db}",
"parentJointIndex": 16,
"position": {
"x": -1.9073486328125e-06,
"y": 0.2300434112548828,
"z": 1.2159347534179688e-05
},
"queryAACube": {
"scale": 0.3082179129123688,
"x": -0.19203892350196838,
"y": -10.429610252380371,
"z": -0.4076632857322693
},
"registrationPoint": {
"x": 0.5,
"y": 0.5,
"z": 0.5
},
"relayParentJoints": false,
"renderInfo": {
"drawCalls": 1,
"hasTransparent": false,
"texturesCount": 5,
"texturesSize": 786432,
"verticesCount": 273
},
"restitution": 0.5,
"rotation": {
"w": 0.5910987257957458,
"x": -0.48726412653923035,
"y": -0.4088631868362427,
"z": 0.49599069356918335
},
"script": "",
"scriptTimestamp": 0,
"serverScripts": "",
"shapeType": "box",
"staticCertificateVersion": 0,
"textures": "",
"type": "Model",
"userData": "{\"Attachment\":{\"action\":\"attach\",\"joint\":\"[LR]ForeArm\",\"attached\":false,\"options\":{\"translation\":{\"x\":0,\"y\":0,\"z\":0},\"scale\":1}},\"grabbableKey\":{\"cloneable\":false,\"grabbable\":true}}",
"velocity": {
"x": 0,
"y": 0,
"z": 0
},
"visible": true
}
}
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst",
"version": 3
},
"Last legends Female": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/8d823be5-6197-4418-b984-eb94160ed956-v1/LLFemale_Clothes.fst",
"version": 3
},
"Matthew": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst",
"version": 3
},
"Priscilla": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst",
"version": 3
},
"Woody": {
"attachments": [
],
"avatarEntites": [
],
"avatarScale": 1,
"avatarUrl": "http://mpassets.highfidelity.com/ad348528-de38-420c-82bb-054cb22163f5-v1/mannequin.fst",
"version": 3
}
}

View file

@ -1,11 +1,10 @@
{
"name": "Keyboard/Mouse to Actions",
"channels": [
{ "from": "Keyboard.A", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.A", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": "!Keyboard.Control", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": "!Keyboard.Control", "to": "Actions.LATERAL_LEFT" },
{ "comment" : "Mouse turn need to be small continuous increments",
"from": { "makeAxis" : [
@ -87,21 +86,41 @@
},
{ "from": { "makeAxis" : [
["Keyboard.A", "Keyboard.TouchpadLeft"],
["Keyboard.D", "Keyboard.TouchpadRight"]
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraFirstPerson", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
["Keyboard.D"]
]
},
"when": ["Application.CameraThirdPerson", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraFirstPerson",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A", "Keyboard.TouchpadLeft"],
["Keyboard.D", "Keyboard.TouchpadRight"]
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraThirdPerson",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": "Keyboard.RightMouseButton",
"to": "Actions.Yaw",

Binary file not shown.

View file

@ -35,8 +35,6 @@
<glyph glyph-name="diclosure-expand" unicode="&#66;" d="M239 187c-3 0-6 1-8 3-5 5-5 12 0 17l42 42-43 44c-5 4-5 12 0 17 4 4 12 4 17 0l60-61-60-59c-2-2-5-3-8-3z"/>
<glyph glyph-name="reload-small" unicode="&#97;" d="M334 253c-9 0-18-7-18-16-3-27-25-47-52-47-29 0-52 24-52 52 0 11 2 27 14 38 6 5 14 9 24 12-5-7-4-16 2-22 3-3 8-4 12-4 5 0 9 1 13 5l28 28c1 2 3 4 3 7 3 6 1 13-3 18l-29 29c-3 3-7 5-12 5-5 0-9-2-12-5-4-3-5-8-5-12 0-5 1-9 5-13l0 0c-20-3-37-11-50-23-16-16-25-38-25-63 0-47 39-86 86-86 45 0 82 33 87 78 1 9-6 18-16 19z"/>
<glyph glyph-name="close-small" unicode="&#67;" d="M291 259l43 44c8 7 8 19 0 27-7 7-19 7-26 0l-44-44-44 44c-7 7-19 7-26 0-8-8-8-20 0-27l43-44-43-43c-8-8-8-20 0-27 7-7 19-7 26 0l44 44 44-44c7-7 19-7 26 0 8 8 8 19 0 27z"/>
<glyph glyph-name="backward" unicode="&#69;" d="M292 349c-5 3-12 3-18-1l-94-71c-4-3-7-8-7-13 0-5 2-10 6-14l95-80c3-2 7-4 11-4 2 0 5 1 7 2 6 3 10 9 10 15l0 151c0 6-4 12-10 15"/>
<glyph glyph-name="reload" unicode="&#70;" d="M365 261c-9 1-17-5-18-15-4-45-43-80-89-80-49 0-89 40-89 89 0 19 4 45 25 65 16 15 39 23 68 25l-15-16c-6-6-6-17 0-24 4-3 8-4 12-4 4 0 9 1 12 5l43 44c2 2 3 4 4 6 2 6 1 13-4 18l-44 44c-6 6-17 6-23 0-7-7-7-17 0-24l15-15c-38-2-69-14-91-35-23-21-36-53-36-88 0-68 55-123 123-123 64 0 116 47 122 110 1 9-5 18-15 18"/>
<glyph glyph-name="minimize" unicode="&#73;" d="M154 282l198 0c10 0 18-8 18-18 0-10-8-18-18-18l-198 0c-10 0-18 8-18 18 0 10 8 18 18 18"/>
<glyph glyph-name="maximize" unicode="&#74;" d="M157 244l77 0 0-75c0-9 8-17 17-17 9 0 17 8 17 17l0 75 75 0c10 0 17 8 17 17 0 10-7 18-17 18l-75 0 0 76c0 10-8 17-17 17-9 0-17-7-17-17l0-76-77 0c-10 0-17-8-17-18 0-9 8-17 17-17z"/>
<glyph glyph-name="maximize-inverted" unicode="&#75;" d="M251 434c-96 0-173-78-173-173 0-96 77-173 173-173 95 0 173 77 173 173 0 95-78 173-173 173z m93-190l-77 0 0-76c0-10-7-17-16-17-9 0-16 7-16 17l0 76-77 0c-10 0-17 8-17 17 0 9 7 17 17 17l77 0 0 76c0 9 7 17 16 17 9 0 16-8 16-17l0-76 77 0c9 0 17-8 17-17 0-9-8-17-17-17z"/>
@ -48,7 +46,6 @@
<glyph glyph-name="script-new" unicode="&#81;" d="M298 80l-145 0c-30 0-55 15-72 43-12 20-16 40-16 41l-3 16 267 0 0-12c0-1 1-15 9-29 10-18 26-27 49-27 13 0 22 4 29 12 16 18 16 54 14 66l0 1 0 206-269 0c-7 0-13 6-13 13 0 7 6 13 13 13l295 0 0-230c1-8 5-57-21-86-12-14-28-21-48-21-41 0-62 23-72 41-5 10-8 19-10 28l-210 0c2-7 5-12 8-18 13-20 29-30 50-30l145 0c7 0 13-7 13-14 0-7-6-13-13-13z m95 260l-180 0c-7 0-14 6-14 14 0 8 7 14 14 14l180 0c7 0 14-6 14-14 0-8-7-14-14-14z m0-59l-135 0c-8 0-14 7-14 15 0 7 6 14 14 14l135 0c7 0 14-7 14-14 0-8-7-15-14-15z m0-58l-180 0c-7 0-14 7-14 14 0 8 7 15 14 15l180 0c7 0 14-7 14-15 0-7-7-14-14-14z m-250 90l0 53c0 9-7 16-16 16-9 0-16-7-16-16l0-53-54 0c-9 0-16-8-16-17 0-9 8-17 16-17l54 0 0-53c0-9 7-16 16-16 9 0 16 8 16 17l0 52 54 0c9 0 16 8 16 17 0 9-8 16-17 16z"/>
<glyph glyph-name="hifi-forum" unicode="&#50;" d="M265 410c-83 0-150-68-150-150 0-24 6-47 16-67l-27-79 80 20c23-16 51-25 81-25 83 0 150 68 150 150 0 83-67 151-150 151z m38-248c-9 0-17 7-17 17 0 7 4 14 12 16l0 46-74 33 0-56c7-2 12-8 12-16 0-10-7-18-17-18-10 0-19 8-19 18 0 7 6 13 10 16l0 111c-4 2-10 9-10 16 0 10 9 17 19 17 9 0 17-7 17-17 0-8-5-14-12-16l0-41 74-33 0 51c-8 3-12 9-12 16 0 10 7 18 17 18 10 0 17-8 17-18 0-7-5-14-12-16l0-110c7-3 12-9 12-17 0-10-7-17-17-17z"/>
<glyph glyph-name="hifi-logo-small" unicode="&#83;" d="M374 374c-32 32-74 49-119 49-46 0-88-17-120-49-32-32-49-74-49-119 0-45 17-87 49-118 32-32 74-49 119-49 45 0 88 17 119 49 32 32 49 73 49 118 1 45-17 87-48 119z m-17-221c-28-28-65-43-103-43-39 0-75 15-103 43-27 27-42 64-42 102 0 39 15 75 42 103 28 28 64 43 103 43 38 0 75-15 103-43 27-28 42-64 42-103 0-39-15-76-42-102z m-145 47c-5 0-9 3-9 6l0 126c0 3 4 7 9 7 6 0 10-4 10-7l0-125c0-4-4-7-10-7z m0 118c-5 0-10 2-14 6-7 7-7 20 0 27 5 5 9 6 14 6 6 0 11-2 14-6 4-4 5-8 5-14 0-5-2-10-5-13-4-4-8-6-14-6z m0-144c-5 0-10 2-14 5-4 5-5 9-5 14 0 6 2 11 5 14 5 4 9 5 14 5 6 0 11-2 14-5 4-4 5-8 5-14 0-5-2-10-5-14-4-3-8-5-14-5z m85 2c-6 0-10 3-10 7l0 121c0 4 4 7 10 7 5 0 9-3 9-7l0-121c0-5-4-7-9-7z m0 120c-6 0-11 2-14 5-8 8-8 20 0 28 4 4 8 5 14 5 5 0 10-2 14-5 4-4 5-9 5-14 0-5-2-11-5-14-4-3-9-5-14-5z m0-144c-6 0-11 2-14 5-8 7-8 20 0 28 4 4 8 5 14 5 5 0 10-2 14-5 4-5 5-9 5-14 0-5-2-11-5-14-4-3-9-5-14-5z m1 73l-85 40 1 18 86-40z"/>
<glyph glyph-name="avatar-1" unicode="&#84;" d="M293 71c-1 0-1 0-1 0-14-1-14-1-16 12-6 43-11 86-16 128-1 2-1 4-1 6-3 0-6 0-9 0-2-10-3-20-4-31-4-36-8-72-12-109-1-7-2-8-9-8-2 0-5 0-7 0 0 74 1 181 1 254-1-1-33 0-44 1-15 0-62 1-79 1-8 0-14 3-18 10-1 2-2 4-4 7 8 0 15 0 22 1 35 1 99 8 100 12 14 11 23 10 36 10 15 0 31 0 46-1 11 0 24 3 37-10 20-10 81-10 123-11 0 0 1 0 1 0-4-12-12-17-25-17-29-1-77-3-127-1 2-73 4-181 6-254z m-32 371c16-6 14-20 13-32 0-5-1-10-1-14-2-11-10-18-20-18-11 0-19 8-20 18-1 6-1 13-2 20-1 7 3 13 11 15 10 3 10 3 19 11z"/>
<glyph glyph-name="placemark" unicode="&#85;" d="M134 98c31-32 73-49 119-49l1 0c45 0 86 16 117 47 31 30 48 71 48 114 1 46-16 88-46 120-9 9-20 17-31 24-3-7-6-15-10-22 5-4 11-8 16-13 15-14 28-32 33-46l1-1c0-1 0-2-1-3l-1 0c-12-7-25-10-38-12-2-1-4-1-7-2l-1 0c0 0-1 0-2 1 0 0-1 1-1 1l0 1c-3 15-6 31-12 46l-7-15c4-11 6-21 8-32l0-1c0-1 0-1 0-2-1-1-1-1-2-1l-1 0-55-3-4 0c0 0-1 0-1 0-1 1-1 2-1 2l0 74c-5 9-8 17-11 26 0-1 0-1 0-1l-1 0 0-97c0 0 0-1 0-2 0-1-1-2-2-2l-1 0c0 0-1 0-1 0l-51 3c-1 0-2 0-2 1-1 1-1 2 0 2l0 1c5 27 13 58 35 83 6 6 12 13 20 16 0 0 1 0 1 0-3 7-6 15-9 22-37-4-71-20-99-47-30-30-47-70-48-113-1-46 16-88 47-120z m198 72c0 4 1 7 1 10l0 0c0 7 1 14 1 21 0 9 0 18 0 27 0 3 0 6 0 9l0 1c-1 1-1 6 5 7 11 3 23 5 34 8l1 0c3 1 7 2 11 2l1 1c1 0 3-1 3-2l0-1c0-1 1-1 1-2 1-3 1-5 2-8 3-14 4-27 3-40-2-12-8-21-18-25-8-3-17-5-25-8-3 0-7-1-10-2-1-1-3-1-4-1-1 0-1 0-2 0l-1-1c0 0 0 0 0 0-1 0-2 1-2 1-1 1-1 1-1 2z m2-17c12 4 23 7 35 10l14 4c1 0 2 0 2-1 1 0 1-2 1-2l-1-2c-7-18-17-34-30-47-13-13-28-24-46-31l-2-1c0 0-1 0-1 0-1 0-1 0-2 0 0 1-1 2-1 3l1 1c0 1 0 1 0 1 0 1 0 2 1 3 6 9 15 24 22 55 1 3 2 6 7 7z m-78 83c0 1 1 3 2 3l3 0 41 2 13 1c0 0 1 0 1 0l1 0c0 0 0-1 1-1l1 0c1 0 2-1 2-3 0-1 0-3 0-5 0-2 0-4 0-6l0-5c1-7 1-15 0-22l0-4c0-5 0-11-1-17 0-1 0-3 0-5 0-1 0-3 0-4 0-2-1-7-7-8-10 0-20-1-30-2l-2 0c-3-1-8-1-12-1-5-1-7-1-8-1-1 0-1 0-2 0l-2 1c-1 1-1 2-1 3l0 74z m0-92c0 1 1 3 2 3l3 0 51 3c1 0 2 0 2-1 1 0 1-1 1-2l0-1c0 0 0-1 0-1 0 0 0 0-1-1 0 0 0-1 0-1l0-1c0-1-1-3-1-4-1-2-2-5-3-7l-1-3c-3-9-7-18-11-27-4-8-9-14-14-19-5-6-13-9-23-10l-3 0c-1 0-1 0-2 1 0 0 0 1 0 2l0 69z m-52-59c-1-1-1-2-2-2 0 0-1 1-1 1l-2 1c-27 6-64 40-78 79l-1 1c0 1 0 2 1 3 1 1 2 1 3 1l13-4c12-4 24-7 36-11 3-1 6-2 7-7 5-24 12-42 23-57l1-2c1-1 1-3 0-3z m40-10c0-1-1-1-1-2-1 0-2 0-2 0 0 0-1 0-1 0-1 0-14 4-20 11-15 19-23 41-29 61 0 0 0 0 0 1l0 1c0 1 0 1 1 2 0 1 1 1 2 1l1 0 29-2 17-1c2-1 3-2 3-3l0-69z m-21 165l18-2c2 0 3-1 3-2l0-75c0-1-1-2-1-2-1-1-2-1-2-1 0 0 0 0-1 0 0 0-14 2-21 2-8 1-17 2-26 3-4 0-6 4-6 7-2 18-3 35-3 47 0 5-1 22-1 22 0 1 1 2 1 2 1 1 1 1 2 1z m-98 32l1 1c12 31 42 59 76 71l3 1c1 0 2 0 3-1 0-1 0-2 0-3l-2-3c-18-25-25-53-31-79l0-1c0-1 0-2-1-2 0 0-1-1-1-1-1 0-1 0-1 0l-1 1c-5 1-11 3-17 4-8 2-18 4-27 8l0 1c-2 0-2 2-2 3z m-8-18l1 1c0 1 1 2 3 2l49-13c1 0 2-1 2-3l2-71c0-1 0-2-1-2-1-1-1-1-2-1 0 0 0 0 0 0l-1 0c-1 0-2 1-3 1l0 0c-2 0-4 1-7 1-16 4-29 9-39 17-6 4-9 8-9 15-1 19 1 37 5 53z m178 42c-8 16-14 31-21 46-8 17-17 35-24 52-14 31 3 65 36 71 28 5 56-16 59-44 0-9-1-18-5-26-14-32-29-64-44-97 0 0-1-1-1-2z m27 117c0 15-12 28-27 28-15 0-27-12-28-27 0-16 13-28 28-28 15 0 27 12 27 27z"/>
<glyph glyph-name="box" unicode="&#86;" d="M318 74l126 89 15-22-126-88z m-137 101l0-99 27 0 0 96z m145-125c-1 0-1 0-2 0l-262 26c-7 1-12 7-12 13l0 263c0 4 1 7 4 10 3 2 7 4 10 3l263-26c7 0 12-6 12-13l0-262c0-4-2-8-4-10-3-3-6-4-9-4z m-250 51l236-23 0 236-236 23z m377 326l-263 26c-3 1-7-1-10-3-3-3-4-6-4-10l0-21c3 2 7 3 11 3 0 0 0 0 1 0 3 2 7 4 11 4 2 0 3 0 5-1l234-23 0-236c0-7-3-10-10-14 1-2 1-5 1-7 1-2 0-3 0-5l21-2c1 0 1 0 2 0 3 0 6 1 8 4 3 2 5 6 5 10l0 262c0 7-5 13-12 13z m-397-64l125 88 16-22-126-88z m262-26l126 88 15-22-126-88z m-256-123l2 27 263-26-3-26z m146 37l0 91-27 0 0-88c9-1 18-2 27-3z"/>
<glyph glyph-name="community" unicode="&#48;" d="M50 175c-4 0-8 2-11 6-4 6-2 14 4 18l24 16 69 48 89-64c6-4 7-13 3-19-5-6-13-7-19-3l-74 53-53-37-24-16c-3-1-5-2-8-2z m130-10l-44 32-47-32-22-14 0-63 135 0 0 60z m120 10c-4 0-9 2-11 6-4 6-3 14 3 18l25 16 68 48 89-64c6-4 7-13 3-19-4-6-13-7-19-3l-73 53-54-37-24-16c-2-1-5-2-7-2z m129-10l-46 32-45-32-22-14 0-63 135 0 0 60z m-256 202c-4 0-9 2-11 5-4 7-3 15 4 19l24 16 68 48 89-65c6-4 7-12 3-18-4-6-12-7-18-3l-74 53-53-37-25-16c-2-2-5-2-7-2z m129-11l-46 32-45-31-22-15 0-62 135 0 0 60z"/>
@ -103,7 +100,6 @@
<glyph glyph-name="lock" unicode="&#57350;" d="M389 233l0 62c0 68-55 124-123 124-69 0-124-56-124-124l0-62c-24-4-44-26-44-52l0-74c0-29 24-52 52-52l230 0c29 0 53 23 53 52l0 74c0 26-18 48-44 52z m-123 129c37 0 67-30 67-67l0-61-135 0 0 61c0 37 31 67 68 67z"/>
<glyph glyph-name="visible" unicode="&#57351;" d="M258 116c-55 0-106 17-147 51-31 25-47 51-47 52-4 7-4 16 1 23 2 4 66 98 195 96 133-3 192-93 195-97 4-6 4-15 0-22 0-1-15-27-46-53-29-23-79-50-151-50 0 0 0 0 0 0z m-148 113c7-7 17-18 30-29 34-27 73-40 118-40 0 0 0 0 0 0 47 0 88 13 122 40 13 10 23 21 29 29-7 7-16 16-30 26-34 25-74 38-119 38-81 2-130-42-150-64z m-27 1z m227-4c0-25-21-46-47-46-26 0-47 21-47 46 0 26 21 47 47 47 26 0 47-21 47-47z"/>
<glyph glyph-name="model" unicode="&#57352;" d="M494 395c-2 5-8 8-13 7l-90-16 45 72c3 5 2 11-1 15-4 4-10 5-15 3l-213-98c-15 5-72 27-111 43 0 0-1 0-1 0 0 0 0 0 0 0 0 0-1 0-1 1 0 0-1 0-1 0 0 0-1 0-1 0 0 0 0 0 0 0-1 0-1 0-2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0 0 0-1-1 0-1 0-2 0 0 0 0 0 0 0 0 0-1 0-1 0 0 0 0-1 0-1-1 0-2 0-3-1 0 0 0 0 0 0 0-1-1-1-1-1 0 0 0 0 0 0 0 0 0-1 0-1-1 0-1 0-1 0 0 0 0 0 0-1 0 0 0 0-1-1l-27-52-33-40c-3-4-3-10 0-15 2-3 6-5 10-5 1 0 2 0 4 1l50 17 40 2-26-51c-3-4-2-9 1-13 1-1 26-30 52-58 15-17 28-30 38-40 6-6 11-11 15-14l-16-61-46-18c-6-3-9-10-6-16 2-5 6-8 11-8 1 0 3 1 4 1l45 18 17-15c2-3 5-4 8-4 4 0 7 2 9 5 5 5 4 12-1 17l-17 15 16 61 76-90c2-2 6-4 9-4 1 0 2 0 3 0l85 23c5 2 8 6 9 11 0 5-2 9-7 12l-136 72 45 91 178 123c5 3 6 9 4 15z m-200-117l-122 55 41 21 181 83z m-148 73l-24 33c16-6 39-15 54-21z m-59-6l-9 13 15 29 27-38 2-2z m36-77l23 44c18-45 35-91 47-121-19 20-45 49-70 77z m194-194l-57 68 105-55z m-94 101c-5 14-42 104-30 77 0-2-21 49-28 66l121-59-48-95z m108 120l43 63 70 16z"/>
<glyph glyph-name="forward" unicode="&#68;" d="M330 278l-95 70c-5 4-12 5-18 2-5-3-9-9-9-16l0-150c0-7 4-13 10-16 2-1 5-2 7-2 4 0 8 2 11 5l95 79c4 4 6 9 6 14-1 5-3 10-7 14"/>
<glyph glyph-name="avatar-2" unicode="&#57353;" d="M256 88c-93 0-169 75-169 168 0 93 76 169 169 169 93 0 169-76 169-169 0-93-76-168-169-168z m0 316c-81 0-148-66-148-148 0-81 67-147 148-147 81 0 148 66 148 147 0 82-67 148-148 148z m97-90l-1 1c-3 3-7 4-10 4-1 0-61-9-86-9-1 0-1 0-2 0-25 0-87 10-87 10-5 0-10-2-13-6l-1-2c-2-3-2-7-1-10 1-4 3-6 6-8 12-5 49-20 60-22 2 0 5 0 6-7 1-8-3-46-7-65-5-17-13-40-13-41-2-6 1-13 7-15l8-3c3-1 6-1 9 1 3 1 5 4 6 7l21 65 20-67c1-3 3-6 6-7 2-1 4-1 5-1 2 0 3 0 5 0l7 3c5 2 8 8 7 14 0 0-6 24-11 44-3 12-4 30-5 45 0 9-1 16-2 22 0 1 0 4 5 5 0 0 1 0 2 0l55 22c4 2 6 5 7 9 1 4 0 8-3 11z m-68 37c0-16-13-29-29-29-16 0-29 13-29 29 0 16 13 29 29 29 16 0 29-13 29-29z"/>
<glyph glyph-name="arrow-dn" unicode="&#53;" d="M258 219l-43 55 86 0z"/>
<glyph glyph-name="arrow-up" unicode="&#54;" d="M258 283l43-55-86 0z"/>
@ -154,4 +150,8 @@
<glyph glyph-name="uninstall" unicode="&#57395;" d="M83 227c-7 0-13-6-13-13l0-78c0-19 16-35 35-35l297 0c19 0 35 16 35 35l0 78c0 7-6 13-13 13-7 0-13-6-13-13l0-78c0-5-4-9-9-9l-297 0c-5 0-9 4-9 9l0 78c0 7-6 13-13 13z m191 47l50 50c5 5 5 14 0 19-5 5-13 5-19 0l-50-50-50 50c-5 5-13 5-19 0-5-5-5-14 0-19l50-50-50-50c-5-5-5-14 0-19 5-5 14-5 19 0l50 50 50-50c5-5 14-5 19 0 5 5 5 14 0 19z"/>
<glyph glyph-name="install" unicode="&#57391;" d="M83 227c-7 0-13-6-13-13l0-78c0-19 16-35 35-35l297 0c19 0 35 16 35 35l0 78c0 7-6 13-13 13-7 0-13-6-13-13l0-78c0-5-4-9-9-9l-297 0c-5 0-9 4-9 9l0 78c0 7-6 13-13 13z m170 171l0-155-33 27c-6 5-14 5-19-1-4-5-4-14 2-18l54-48c3-2 5-3 8-3 3 0 7 1 9 3l54 48c5 4 6 13 1 18-4 5-13 6-18 1l-32-27 0 154c0 8-6 13-13 13-7 0-13-5-13-12z"/>
<glyph glyph-name="ellipsis-vertical" unicode="&#57396;" d="M276 178c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z m0 78c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z m0 78c0-14-11-24-24-24-14 0-25 10-25 24 0 13 11 24 25 24 13 0 24-11 24-24z"/>
<glyph glyph-name="backward" unicode="&#69;" d="M292 349c-5 3-12 3-18-1l-94-71c-4-3-7-8-7-13 0-5 2-10 6-14l95-80c3-2 7-4 11-4 2 0 5 1 7 2 6 3 10 9 10 15l0 151c0 6-4 12-10 15"/>
<glyph glyph-name="40-reload" unicode="&#70;" d="M365 261c-9 1-17-5-18-15-4-45-43-80-89-80-49 0-89 40-89 89 0 19 4 45 25 65 16 15 39 23 68 25l-15-16c-6-6-6-17 0-24 4-3 8-4 12-4 4 0 9 1 12 5l43 44c2 2 3 4 4 6 2 6 1 13-4 18l-44 44c-6 6-17 6-23 0-7-7-7-17 0-24l15-15c-38-2-69-14-91-35-23-21-36-53-36-88 0-68 55-123 123-123 64 0 116 47 122 110 1 9-5 18-15 18"/>
<glyph glyph-name="forward" unicode="&#68;" d="M330 278l-95 70c-5 4-12 5-18 2-5-3-9-9-9-16l0-150c0-7 4-13 10-16 2-1 5-2 7-2 4 0 8 2 11 5l95 79c4 4 6 9 6 14-1 5-3 10-7 14"/>
<glyph glyph-name="avatar-1" unicode="&#84;" d="M396 344l-2 2c-4 4-9 5-15 5-1 0-88-13-124-14-1 0-2 0-3 0-37 0-126 15-127 15-7 1-14-2-18-8l-2-4c-3-4-3-9-2-14 2-5 5-9 10-11 16-7 69-22 85-29 3-1 10-4 10-14 1-11-4-67-10-93-7-26-18-60-19-60-3-9 2-19 11-22l11-4c4-2 9-1 13 1 5 2 8 6 9 10l31 94 28-96c2-5 5-9 9-11 3-1 5-2 8-2 2 0 4 0 7 1l10 4c8 3 12 12 10 20 0 1-8 36-16 65-4 17-6 43-7 64-1 13-1 21-3 30 0 1 2 11 10 14 10 4 81 29 80 28 6 2 10 7 11 13 1 6-1 12-5 16z m-98 54c0-24-19-43-43-43-24 0-43 19-43 43 0 23 19 42 43 42 24 0 43-19 43-42z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View file

@ -127,14 +127,6 @@
<div class="icon icon-close-small"></div>
<input type="text" readonly="readonly" value="close-small">
</li>
<li>
<div class="icon icon-backward"></div>
<input type="text" readonly="readonly" value="backward">
</li>
<li>
<div class="icon icon-reload"></div>
<input type="text" readonly="readonly" value="reload">
</li>
<li>
<div class="icon icon-minimize"></div>
<input type="text" readonly="readonly" value="minimize">
@ -179,10 +171,6 @@
<div class="icon icon-hifi-logo-small"></div>
<input type="text" readonly="readonly" value="hifi-logo-small">
</li>
<li>
<div class="icon icon-avatar-1"></div>
<input type="text" readonly="readonly" value="avatar-1">
</li>
<li>
<div class="icon icon-placemark"></div>
<input type="text" readonly="readonly" value="placemark">
@ -399,10 +387,6 @@
<div class="icon icon-model"></div>
<input type="text" readonly="readonly" value="model">
</li>
<li>
<div class="icon icon-forward"></div>
<input type="text" readonly="readonly" value="forward">
</li>
<li>
<div class="icon icon-avatar-2"></div>
<input type="text" readonly="readonly" value="avatar-2">
@ -603,6 +587,22 @@
<div class="icon icon-ellipsis-vertical"></div>
<input type="text" readonly="readonly" value="ellipsis-vertical">
</li>
<li>
<div class="icon icon-backward"></div>
<input type="text" readonly="readonly" value="backward">
</li>
<li>
<div class="icon icon-40-reload"></div>
<input type="text" readonly="readonly" value="40-reload">
</li>
<li>
<div class="icon icon-forward"></div>
<input type="text" readonly="readonly" value="forward">
</li>
<li>
<div class="icon icon-avatar-1"></div>
<input type="text" readonly="readonly" value="avatar-1">
</li>
</ul>
<h2>Character mapping</h2>
<ul class="glyphs character-mapping">
@ -718,14 +718,6 @@
<div data-icon="C" class="icon"></div>
<input type="text" readonly="readonly" value="C">
</li>
<li>
<div data-icon="E" class="icon"></div>
<input type="text" readonly="readonly" value="E">
</li>
<li>
<div data-icon="F" class="icon"></div>
<input type="text" readonly="readonly" value="F">
</li>
<li>
<div data-icon="I" class="icon"></div>
<input type="text" readonly="readonly" value="I">
@ -770,10 +762,6 @@
<div data-icon="S" class="icon"></div>
<input type="text" readonly="readonly" value="S">
</li>
<li>
<div data-icon="T" class="icon"></div>
<input type="text" readonly="readonly" value="T">
</li>
<li>
<div data-icon="U" class="icon"></div>
<input type="text" readonly="readonly" value="U">
@ -990,10 +978,6 @@
<div data-icon="&#xe008;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe008;">
</li>
<li>
<div data-icon="D" class="icon"></div>
<input type="text" readonly="readonly" value="D">
</li>
<li>
<div data-icon="&#xe009;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe009;">
@ -1194,6 +1178,22 @@
<div data-icon="&#xe034;" class="icon"></div>
<input type="text" readonly="readonly" value="&amp;#xe034;">
</li>
<li>
<div data-icon="E" class="icon"></div>
<input type="text" readonly="readonly" value="E">
</li>
<li>
<div data-icon="F" class="icon"></div>
<input type="text" readonly="readonly" value="F">
</li>
<li>
<div data-icon="D" class="icon"></div>
<input type="text" readonly="readonly" value="D">
</li>
<li>
<div data-icon="T" class="icon"></div>
<input type="text" readonly="readonly" value="T">
</li>
</ul>
</div>
<script>(function() {

View file

@ -122,12 +122,6 @@
.icon-close-small:before {
content: "\43";
}
.icon-backward:before {
content: "\45";
}
.icon-reload:before {
content: "\46";
}
.icon-minimize:before {
content: "\49";
}
@ -161,9 +155,6 @@
.icon-hifi-logo-small:before {
content: "\53";
}
.icon-avatar-1:before {
content: "\54";
}
.icon-placemark:before {
content: "\55";
}
@ -326,9 +317,6 @@
.icon-model:before {
content: "\e008";
}
.icon-forward:before {
content: "\44";
}
.icon-avatar-2:before {
content: "\e009";
}
@ -479,3 +467,15 @@
.icon-ellipsis-vertical:before {
content: "\e034";
}
.icon-backward:before {
content: "\45";
}
.icon-40-reload:before {
content: "\46";
}
.icon-forward:before {
content: "\44";
}
.icon-avatar-1:before {
content: "\54";
}

View file

@ -0,0 +1,3 @@
<svg width="123" height="126" viewBox="0 0 123 126" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.85" d="M120.429 69.9586C124.864 79.9597 123.148 89.9608 118.427 99.3905C116.71 102.819 114.421 105.963 112.418 109.249C101.689 114.678 90.8162 120.107 80.0867 125.679C73.0768 126.822 66.6391 124.822 60.7737 121.107C52.7623 115.821 45.1802 110.249 37.455 104.677C31.3034 100.248 25.1518 95.6758 18.8572 91.2467C13.421 87.532 11.4181 81.5313 13.9932 76.245C16.7113 70.673 22.4337 68.5299 28.8714 70.5301C31.8756 71.5302 35.0229 72.3874 38.4564 73.3875C35.5952 67.8155 32.8771 62.2434 29.8728 56.6714C29.5867 56.0999 28.013 55.8141 27.1547 55.8141C15.4238 55.3855 5.69575 48.3847 1.54703 37.3835C-2.17252 27.2395 0.974791 15.0953 9.27224 7.52302C19.0003 -1.33511 33.3062 -2.4781 44.0357 4.66556C54.7652 11.8092 59.0569 24.6678 54.479 37.5264C54.336 37.955 54.0499 38.5265 53.6207 39.3837C58.6278 37.5264 62.9196 38.0979 66.6391 41.0982C73.3629 34.5261 77.0824 33.9546 84.2354 37.955C87.0966 36.2405 89.5286 34.2403 92.3898 33.3831C97.2538 31.8115 102.547 33.9546 104.979 38.5265C110.129 48.9562 115.709 59.2431 120.429 69.9586ZM113.133 97.2474C117.282 88.5321 118.713 79.674 114.564 70.673C109.986 60.9576 104.979 51.528 100.115 41.9555C98.5414 38.9551 95.251 37.8121 92.3898 39.3837C89.3856 40.9553 88.3841 43.9557 89.9578 47.0989C91.8176 50.8136 93.6773 54.5283 95.5371 58.243C96.5385 60.1003 96.9677 61.9577 94.6788 63.1007C92.3898 64.2437 91.2453 62.6721 90.2439 60.8147C87.5258 55.3855 84.8077 50.0992 82.0895 44.67C80.5159 41.6697 77.2255 40.5267 74.3643 41.9555C71.5031 43.3842 70.3587 46.6703 71.9323 49.6706C73.7921 53.5282 75.6518 57.2429 77.6547 60.9576C78.513 62.8149 78.7992 64.5294 76.7963 65.6724C74.6504 66.8154 73.3629 65.3867 72.3615 63.5293C69.6433 58.1001 66.9252 52.8138 64.2071 47.3846C62.4904 44.0985 59.3431 43.0984 56.3388 44.67C53.3346 46.2416 52.4762 49.242 54.0499 52.5281C55.7666 56.0999 57.6264 59.8146 59.4861 63.3864C60.7737 65.9581 60.4875 67.8155 58.7708 68.5299C56.4819 69.3871 55.3374 67.9584 54.336 66.101C52.1901 61.672 49.9011 57.3858 47.7552 53.0996C42.8912 43.5271 38.0272 33.9546 33.1632 24.3821C31.3034 20.6674 27.2977 19.8101 24.1504 22.3818C22.0045 24.0963 21.4323 26.8109 23.006 29.6684C26.8686 37.5264 30.8742 45.2415 34.8799 53.0996C38.8856 60.9576 42.7482 68.6727 46.7538 76.3879C47.6122 77.9595 48.4705 79.5311 46.8969 80.6741C45.8955 81.3884 44.0357 81.5313 42.7482 81.1027C37.455 79.5311 32.1618 77.5309 26.8686 75.8164C23.7212 74.8163 21.0031 75.8164 19.5725 78.2452C18.1419 80.6741 18.7142 83.3887 21.1462 85.5318C21.5754 85.9604 22.1476 86.2461 22.7198 86.6747C26.8686 89.6751 31.1603 92.3897 35.3091 95.5329C42.7482 101.105 50.0442 107.248 57.9125 112.535C62.2043 115.535 67.2113 117.535 72.0754 119.536C74.9365 120.821 77.9408 120.25 80.9451 118.678C88.2411 114.821 95.5371 110.82 103.119 107.534C107.983 105.391 110.988 101.819 113.133 97.2474ZM46.8969 38.9551C52.1901 31.8115 51.3317 20.6674 45.3232 13.6666C38.4564 5.66567 27.2977 3.66545 17.5697 8.66601C8.41388 13.5237 3.69292 24.0963 6.41105 33.8117C8.84306 42.9556 17.7128 50.0992 26.4394 49.6706C23.5782 43.9557 20.717 38.3836 17.8558 32.8116C14.5654 26.2394 16.5683 19.5244 22.7198 16.524C28.7283 13.5237 35.166 15.9525 38.4564 22.3818C41.1745 27.811 43.8926 33.2402 46.8969 38.9551Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<g>
<path d="M78.3,31.3l-0.4-0.4c-0.8-0.8-2-1.2-3.1-1c-0.2,0-18.6,2.7-26.2,2.8c-0.2,0-0.3,0-0.4,0c-7.8,0-26.6-3-26.8-3.1
c-1.5-0.2-3,0.4-3.7,1.7l-0.5,0.8c-0.6,0.9-0.7,2-0.4,3c0.3,1,1.1,1.8,2,2.3c3.4,1.5,14.9,6.3,18.2,6.7c0.6,0.1,1.7,0.2,1.8,2.2
c0.2,2.4-0.8,14.1-2.2,19.7c-1.4,5.4-3.8,12.5-3.9,12.6c-0.7,1.9,0.3,4,2.2,4.7l2.3,0.8c0.9,0.3,2,0.3,2.8-0.2
c0.9-0.4,1.6-1.2,1.9-2.2l6.4-19.7l6,20.3c0.3,1,1,1.8,1.9,2.3c0.5,0.2,1.1,0.4,1.6,0.4c0.5,0,0.9-0.1,1.3-0.2l2.1-0.8
c1.7-0.7,2.7-2.5,2.2-4.3c0-0.1-1.8-7.4-3.3-13.5c-0.9-3.7-1.3-9.2-1.6-13.6c-0.2-2.7-0.3-5-0.6-6.8c-0.1-0.3-0.2-1.1,1.3-1.4
c0.3,0,0.4-0.1,0.6-0.2l16.9-6.7c1.2-0.5,2-1.5,2.3-2.7C79.5,33.5,79.2,32.2,78.3,31.3z"/>
<ellipse cx="48.7" cy="20" rx="9" ry="9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 96 96" style="enable-background:new 0 0 96 96;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<path class="st0" d="M78.3,31.3l-0.4-0.4c-0.8-0.8-2-1.2-3.1-1c-0.2,0-18.6,2.7-26.2,2.8c-0.2,0-0.3,0-0.4,0
c-7.8,0-26.6-3-26.8-3.1c-1.5-0.2-3,0.4-3.7,1.7l-0.5,0.8c-0.6,0.9-0.7,2-0.4,3c0.3,1,1.1,1.8,2,2.3c3.4,1.5,14.9,6.3,18.2,6.7
c0.6,0.1,1.7,0.2,1.8,2.2c0.2,2.4-0.8,14.1-2.2,19.7c-1.4,5.4-3.8,12.5-3.9,12.6c-0.7,1.9,0.3,4,2.2,4.7l2.3,0.8
c0.9,0.3,2,0.3,2.8-0.2c0.9-0.4,1.6-1.2,1.9-2.2l6.4-19.7l6,20.3c0.3,1,1,1.8,1.9,2.3c0.5,0.2,1.1,0.4,1.6,0.4
c0.5,0,0.9-0.1,1.3-0.2l2.1-0.8c1.7-0.7,2.7-2.5,2.2-4.3c0-0.1-1.8-7.4-3.3-13.5c-0.9-3.7-1.3-9.2-1.6-13.6c-0.2-2.7-0.3-5-0.6-6.8
c-0.1-0.3-0.2-1.1,1.3-1.4c0.3,0,0.4-0.1,0.6-0.2l16.9-6.7c1.2-0.5,2-1.5,2.3-2.7C79.5,33.5,79.2,32.2,78.3,31.3z"/>
<ellipse class="st0" cx="48.7" cy="20" rx="9" ry="9"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,9 @@
<svg width="24" height="22" viewBox="0 0 24 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Canvas" fill="none">
<g id="Vector">
<path d="M 4.29197 19.3019C 4.20267 19.3019 4.11337 19.2697 4.02407 19.2052C 3.96826 19.1622 3.81199 19.0548 3.90129 18.6894L 4.21383 17.4427C 4.55987 16.0778 4.89474 14.7236 5.24078 13.3588C 5.43054 12.6064 5.20729 11.9509 4.60452 11.4672C 3.80082 10.8117 2.9748 10.1453 2.18227 9.51126C 1.54601 8.99539 0.898591 8.46878 0.262332 7.95292C -0.0278912 7.72723 -0.0167287 7.57677 0.0167586 7.41556C 0.0390835 7.32958 0.0948956 7.07165 0.53023 7.05015C 2.65109 6.91044 4.51522 6.78147 6.23423 6.66326C 7.02677 6.60952 7.6407 6.19038 7.93092 5.48106C 8.34393 4.51382 8.75694 3.54657 9.15879 2.56858C 9.47134 1.83777 9.78389 1.09621 10.0964 0.365404C 10.2192 0.0752303 10.3643 0.0107472 10.5987 1.02493e-08L 10.6099 1.02493e-08C 10.8108 1.02493e-08 10.9671 0.193449 11.0452 0.365404L 11.8266 2.20317C 12.2954 3.29939 12.7643 4.3956 13.2219 5.50256C 13.5121 6.17963 14.1038 6.59877 14.8628 6.65251C 15.7558 6.71699 16.6488 6.78147 17.5195 6.83521C 18.0664 6.86745 18.6245 6.91044 19.1715 6.94268C 19.2943 6.95343 19.4171 6.96418 19.5399 6.96418C 19.9752 6.99642 20.377 7.01791 20.79 7.07165C 21.1249 7.11464 21.1584 7.46929 21.1584 7.57677L 21.1584 7.60901C 21.1584 7.71648 20.9686 7.87769 20.8905 7.94217C 20.4217 8.31832 19.9417 8.68373 19.4729 9.05988C 18.5129 9.81218 17.5306 10.586 16.5595 11.3383C 15.8563 11.8864 15.5995 12.6064 15.8116 13.4447C 16.1242 14.6377 16.4255 15.8521 16.7269 17.0343C 16.8609 17.5716 16.9948 18.109 17.1288 18.6464C 17.1734 18.8398 17.1511 19.0225 17.0506 19.1515C 16.9725 19.2375 16.872 19.2912 16.7381 19.2912C 16.7158 19.2912 16.6934 19.2912 16.6711 19.2912C 16.6488 19.2912 16.5818 19.2697 16.4702 19.2052C 14.7958 18.2272 13.1438 17.2492 11.5587 16.3035C 11.2238 16.0993 10.8666 16.0026 10.5094 16.0026C 10.1522 16.0026 9.79505 16.11 9.44901 16.3142C 8.24347 17.0343 7.02677 17.7651 5.84355 18.4744L 4.61568 19.2052C 4.49289 19.2697 4.38127 19.3019 4.29197 19.3019Z" transform="translate(1.5 1)" fill="#FFB017"/>
<path d="M 4.29197 19.3019C 4.20267 19.3019 4.11337 19.2697 4.02407 19.2052C 3.96826 19.1622 3.81199 19.0548 3.90129 18.6894L 4.21383 17.4427C 4.55987 16.0778 4.89474 14.7236 5.24078 13.3588C 5.43054 12.6064 5.20729 11.9509 4.60452 11.4672C 3.80082 10.8117 2.9748 10.1453 2.18227 9.51126C 1.54601 8.99539 0.898591 8.46878 0.262332 7.95292C -0.0278912 7.72723 -0.0167287 7.57677 0.0167586 7.41556C 0.0390835 7.32958 0.0948956 7.07165 0.53023 7.05015C 2.65109 6.91044 4.51522 6.78147 6.23423 6.66326C 7.02677 6.60952 7.6407 6.19038 7.93092 5.48106C 8.34393 4.51382 8.75694 3.54657 9.15879 2.56858C 9.47134 1.83777 9.78389 1.09621 10.0964 0.365404C 10.2192 0.0752303 10.3643 0.0107472 10.5987 1.02493e-08L 10.6099 1.02493e-08C 10.8108 1.02493e-08 10.9671 0.193449 11.0452 0.365404L 11.8266 2.20317C 12.2954 3.29939 12.7643 4.3956 13.2219 5.50256C 13.5121 6.17963 14.1038 6.59877 14.8628 6.65251C 15.7558 6.71699 16.6488 6.78147 17.5195 6.83521C 18.0664 6.86745 18.6245 6.91044 19.1715 6.94268C 19.2943 6.95343 19.4171 6.96418 19.5399 6.96418C 19.9752 6.99642 20.377 7.01791 20.79 7.07165C 21.1249 7.11464 21.1584 7.46929 21.1584 7.57677L 21.1584 7.60901C 21.1584 7.71648 20.9686 7.87769 20.8905 7.94217C 20.4217 8.31832 19.9417 8.68373 19.4729 9.05988C 18.5129 9.81218 17.5306 10.586 16.5595 11.3383C 15.8563 11.8864 15.5995 12.6064 15.8116 13.4447C 16.1242 14.6377 16.4255 15.8521 16.7269 17.0343C 16.8609 17.5716 16.9948 18.109 17.1288 18.6464C 17.1734 18.8398 17.1511 19.0225 17.0506 19.1515C 16.9725 19.2375 16.872 19.2912 16.7381 19.2912C 16.7158 19.2912 16.6934 19.2912 16.6711 19.2912C 16.6488 19.2912 16.5818 19.2697 16.4702 19.2052C 14.7958 18.2272 13.1438 17.2492 11.5587 16.3035C 11.2238 16.0993 10.8666 16.0026 10.5094 16.0026C 10.1522 16.0026 9.79505 16.11 9.44901 16.3142C 8.24347 17.0343 7.02677 17.7651 5.84355 18.4744L 4.61568 19.2052C 4.49289 19.2697 4.38127 19.3019 4.29197 19.3019Z" stroke-width="1.75" transform="translate(1.5 1)" stroke="#121212"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,8 @@
<svg width="24" height="22" viewBox="0 0 24 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Canvas" fill="none">
<g id="Vector">
<path d="M 4.29197 19.3019C 4.20267 19.3019 4.11337 19.2697 4.02407 19.2052C 3.96826 19.1622 3.81199 19.0548 3.90129 18.6894L 4.21383 17.4427C 4.55987 16.0778 4.89474 14.7236 5.24078 13.3588C 5.43054 12.6064 5.20729 11.9509 4.60452 11.4672C 3.80082 10.8117 2.9748 10.1453 2.18227 9.51126C 1.54601 8.99539 0.898591 8.46878 0.262332 7.95292C -0.0278912 7.72723 -0.0167287 7.57677 0.0167586 7.41556C 0.0390835 7.32958 0.0948956 7.07165 0.53023 7.05015C 2.65109 6.91044 4.51522 6.78147 6.23423 6.66326C 7.02677 6.60952 7.6407 6.19038 7.93092 5.48106C 8.34393 4.51382 8.75694 3.54657 9.15879 2.56858C 9.47134 1.83777 9.78389 1.09621 10.0964 0.365404C 10.2192 0.0752303 10.3643 0.0107472 10.5987 1.02493e-08L 10.6099 1.02493e-08C 10.8108 1.02493e-08 10.9671 0.193449 11.0452 0.365404L 11.8266 2.20317C 12.2954 3.29939 12.7643 4.3956 13.2219 5.50256C 13.5121 6.17963 14.1038 6.59877 14.8628 6.65251C 15.7558 6.71699 16.6488 6.78147 17.5195 6.83521C 18.0664 6.86745 18.6245 6.91044 19.1715 6.94268C 19.2943 6.95343 19.4171 6.96418 19.5399 6.96418C 19.9752 6.99642 20.377 7.01791 20.79 7.07165C 21.1249 7.11464 21.1584 7.46929 21.1584 7.57677L 21.1584 7.60901C 21.1584 7.71648 20.9686 7.87769 20.8905 7.94217C 20.4217 8.31832 19.9417 8.68373 19.4729 9.05988C 18.5129 9.81218 17.5306 10.586 16.5595 11.3383C 15.8563 11.8864 15.5995 12.6064 15.8116 13.4447C 16.1242 14.6377 16.4255 15.8521 16.7269 17.0343C 16.8609 17.5716 16.9948 18.109 17.1288 18.6464C 17.1734 18.8398 17.1511 19.0225 17.0506 19.1515C 16.9725 19.2375 16.872 19.2912 16.7381 19.2912C 16.7158 19.2912 16.6934 19.2912 16.6711 19.2912C 16.6488 19.2912 16.5818 19.2697 16.4702 19.2052C 14.7958 18.2272 13.1438 17.2492 11.5587 16.3035C 11.2238 16.0993 10.8666 16.0026 10.5094 16.0026C 10.1522 16.0026 9.79505 16.11 9.44901 16.3142C 8.24347 17.0343 7.02677 17.7651 5.84355 18.4744L 4.61568 19.2052C 4.49289 19.2697 4.38127 19.3019 4.29197 19.3019Z" stroke-width="1.75" transform="translate(1.5 1)" stroke="#121212"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 134 134" style="enable-background:new 0 0 134 134;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1FC6A6;}
</style>
<path class="st0" d="M5,0h124c2.8,0,5,2.2,5,5v124c0,2.8-2.2,5-5,5H5c-2.8,0-5-2.2-5-5V5C0,2.2,2.2,0,5,0z"/>
<path d="M100.5,99c-9.2,9.2-20.3,13.8-33.5,13.8S42.6,108.2,33.3,99c-9.2-9.2-13.8-20.3-13.8-33.5s4.6-24.3,13.8-33.5
C42.6,22.7,53.9,18,67,18s24.3,4.7,33.5,14.1c9.4,9.2,14.1,20.3,14.1,33.5S109.8,89.9,100.5,99z M96.3,36.3
C88.2,28.1,78.4,23.9,67,23.9s-21.3,4.1-29.5,12.4c-8.1,8.1-12.1,17.8-12.1,29.3s4,21.2,12.1,29.3c8.3,8.1,18.1,12.1,29.5,12.1
s21.2-4,29.3-12.1c8.3-8.1,12.4-17.8,12.4-29.3S104.5,44.4,96.3,36.3z M94.3,49.3L94,49c-0.8-0.8-1.7-1.1-2.8-1.1l-1.7,0.3
c-1.3,0.2-3,0.5-5.1,0.8c-1.9,0.2-3.8,0.4-5.9,0.6c-2.1,0.2-4.2,0.4-6.5,0.6c-2.1,0.2-3.8,0.3-5.1,0.3h-0.6L42,47.6
c-1.7,0-2.9,0.6-3.7,1.7L38,49.8c-0.6,0.8-0.7,1.7-0.3,2.8c0.2,0.9,0.8,1.7,1.7,2.3c8.8,3.8,14.4,5.8,16.9,6.2c0.9,0,1.5,0.7,1.7,2
c0.4,3.8-0.3,9.8-2,18.3c-0.4,1.3-0.9,3-1.7,5.1c-0.6,1.9-1,3.4-1.4,4.5l-0.6,2c-0.8,2.1-0.1,3.5,2,4.2l2.3,0.8
c0.9,0.4,1.8,0.3,2.5-0.3c0.8-0.2,1.3-0.8,1.7-2l5.9-18.3l5.6,18.8c0.4,1.1,0.9,1.8,1.7,2c0.4,0.2,0.8,0.3,1.4,0.3h1.4l2-0.8
c1.7-0.8,2.3-2.1,2-3.9l-3.1-12.4c-0.6-2.1-1-6.3-1.4-12.7c0-1.9-0.2-3.9-0.6-6.2c0-0.8,0.5-1.2,1.4-1.4h0.6l15.5-6.2
c0.9-0.6,1.6-1.4,2-2.5S95.2,50.2,94.3,49.3z M72.6,33.2c1.7,1.5,2.5,3.4,2.5,5.6s-0.8,4.2-2.5,5.9C71.1,46.3,69.3,47,67,47
s-4.2-0.8-5.9-2.3c-1.5-1.7-2.3-3.7-2.3-5.9s0.8-4.1,2.3-5.6c1.7-1.7,3.7-2.5,5.9-2.5S71.1,31.5,72.6,33.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,76 @@
//
// Web3DOverlay.qml
//
// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
Item {
property string url
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#262626" }
GradientStop { position: 1.0; color: "#000000" }
}
}
function shortUrl(url) {
var hostBegin = url.indexOf("://");
if (hostBegin > -1) {
url = url.substring(hostBegin + 3);
}
var portBegin = url.indexOf(":");
if (portBegin > -1) {
url = url.substring(0, portBegin);
}
var pathBegin = url.indexOf("/");
if (pathBegin > -1) {
url = url.substring(0, pathBegin);
}
if (url.length > 45) {
url = url.substring(0, 45);
}
return url;
}
Text {
id: urlText
text: shortUrl(url)
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.rightMargin: 10
anchors.leftMargin: 10
font.family: "Cairo"
font.weight: Font.DemiBold
font.pointSize: 48
fontSizeMode: Text.Fit
color: "#FFFFFF"
minimumPixelSize: 5
}
Image {
id: hand
source: "../../icons/hand.svg"
width: 300
height: 300
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: 100
anchors.rightMargin: 100
}
}

View file

@ -0,0 +1,288 @@
//
// InteractiveWindow.qml
//
// Created by Thijs Wenker on 2018-06-25
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.3
import "windows" as Windows
import "controls"
import "controls-uit" as Controls
import "styles"
import "styles-uit"
Windows.Window {
id: root;
HifiConstants { id: hifi }
title: "InteractiveWindow";
resizable: true;
// Virtual window visibility
shown: false;
focus: true;
property var channel;
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false;
signal selfDestruct();
property var flags: 0;
property var source;
property var dynamicContent;
property var nativeWindow;
// custom visibility flag for interactiveWindow to proxy virtualWindow.shown / nativeWindow.visible
property var interactiveWindowVisible: true;
property point interactiveWindowPosition;
property size interactiveWindowSize;
// Keyboard control properties in case needed by QML content.
property bool keyboardEnabled: false;
property bool keyboardRaised: false;
property bool punctuationMode: false;
property int presentationMode: 0;
property var initialized: false;
onSourceChanged: {
if (dynamicContent) {
dynamicContent.destroy();
dynamicContent = null;
}
QmlSurface.load(source, contentHolder, function(newObject) {
dynamicContent = newObject;
if (dynamicContent && dynamicContent.anchors) {
dynamicContent.anchors.fill = contentHolder;
}
});
}
function updateInteractiveWindowPositionForMode() {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
x = interactiveWindowPosition.x;
y = interactiveWindowPosition.y;
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) {
// default position for native window in center of main application window
nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2));
nativeWindow.y = Math.floor(Window.y + (Window.innerHeight / 2) - (interactiveWindowSize.height / 2));
} else {
nativeWindow.x = interactiveWindowPosition.x;
nativeWindow.y = interactiveWindowPosition.y;
}
}
}
function updateInteractiveWindowSizeForMode() {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
width = interactiveWindowSize.width;
height = interactiveWindowSize.height;
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
nativeWindow.width = interactiveWindowSize.width;
nativeWindow.height = interactiveWindowSize.height;
}
}
function updateContentParent() {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
contentHolder.parent = root;
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
contentHolder.parent = nativeWindow.contentItem;
}
}
function setupPresentationMode() {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
if (nativeWindow) {
nativeWindow.setVisible(false);
}
updateContentParent();
updateInteractiveWindowPositionForMode();
shown = interactiveWindowVisible;
} else if (presentationMode === Desktop.PresentationMode.NATIVE) {
shown = false;
if (nativeWindow) {
updateContentParent();
updateInteractiveWindowPositionForMode();
nativeWindow.setVisible(interactiveWindowVisible);
}
} else if (presentationMode === modeNotSet) {
console.error("presentationMode should be set.");
}
}
Component.onCompleted: {
// Fix for parent loss on OSX:
parent.heightChanged.connect(updateContentParent);
parent.widthChanged.connect(updateContentParent);
x = interactiveWindowPosition.x;
y = interactiveWindowPosition.y;
width = interactiveWindowSize.width;
height = interactiveWindowSize.height;
nativeWindow = Qt.createQmlObject('
import QtQuick 2.3;
import QtQuick.Window 2.3;
Window {
id: root;
Rectangle {
color: hifi.colors.baseGray
anchors.fill: parent
}
}', root, 'InteractiveWindow.qml->nativeWindow');
nativeWindow.title = root.title;
var nativeWindowFlags = Qt.Window |
Qt.WindowTitleHint |
Qt.WindowSystemMenuHint |
Qt.WindowCloseButtonHint |
Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint;
if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) {
nativeWindowFlags |= Qt.WindowStaysOnTopHint;
}
nativeWindow.flags = nativeWindowFlags;
nativeWindow.x = interactiveWindowPosition.x;
nativeWindow.y = interactiveWindowPosition.y;
nativeWindow.width = interactiveWindowSize.width;
nativeWindow.height = interactiveWindowSize.height;
nativeWindow.xChanged.connect(function() {
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
}
});
nativeWindow.yChanged.connect(function() {
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
}
});
nativeWindow.widthChanged.connect(function() {
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
}
});
nativeWindow.heightChanged.connect(function() {
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
}
});
nativeWindow.closing.connect(function(closeEvent) {
closeEvent.accepted = false;
windowClosed();
});
// finally set the initial window mode:
setupPresentationMode();
initialized = true;
}
Component.onDestruction: {
parent.heightChanged.disconnect(updateContentParent);
parent.widthChanged.disconnect(updateContentParent);
}
// Handle message traffic from the script that launched us to the loaded QML
function fromScript(message) {
if (root.dynamicContent && root.dynamicContent.fromScript) {
root.dynamicContent.fromScript(message);
}
}
function show() {
interactiveWindowVisible = true;
raiseWindow();
}
function raiseWindow() {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
raise();
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
nativeWindow.raise();
}
}
// Handle message traffic from our loaded QML to the script that launched us
signal sendToScript(var message);
onDynamicContentChanged: {
if (dynamicContent && dynamicContent.sendToScript) {
dynamicContent.sendToScript.connect(sendToScript);
}
}
onInteractiveWindowVisibleChanged: {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
shown = interactiveWindowVisible;
} else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
if (!nativeWindow.visible && interactiveWindowVisible) {
nativeWindow.showNormal();
} else {
nativeWindow.setVisible(interactiveWindowVisible);
}
}
}
onTitleChanged: {
if (nativeWindow) {
nativeWindow.title = title;
}
}
onXChanged: {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
interactiveWindowPosition = Qt.point(x, interactiveWindowPosition.y);
}
}
onYChanged: {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y);
}
}
onWidthChanged: {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
interactiveWindowSize = Qt.size(width, interactiveWindowSize.height);
}
}
onHeightChanged: {
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
interactiveWindowSize = Qt.size(interactiveWindowSize.width, height);
}
}
onPresentationModeChanged: {
if (initialized) {
setupPresentationMode();
}
}
onWindowClosed: {
// set invisible on close, to make it not re-appear unintended after switching PresentationMode
interactiveWindowVisible = false;
if ((flags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
selfDestruct();
}
}
Item {
id: contentHolder
anchors.fill: parent
}
}

View file

@ -12,7 +12,7 @@ Rectangle {
}
Label {
text: OverlayWindowTestString
text: "OverlayWindowTestString"
anchors.centerIn: parent
}
}

View file

@ -19,6 +19,8 @@ Original.Button {
property int color: 0
property int colorScheme: hifi.colorSchemes.light
property int fontSize: hifi.fontSizes.buttonLabel
property alias implicitTextWidth: buttonText.implicitWidth
property string buttonGlyph: "";
width: hifi.dimensions.buttonWidth
@ -108,7 +110,7 @@ Original.Button {
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: hifi.fontSizes.buttonLabel
size: control.fontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text

View file

@ -46,6 +46,7 @@ FocusScope {
hoverEnabled: true
visible: true
height: hifi.fontSizes.textFieldInput + 13 // Match height of TextField control.
textRole: "text"
function previousItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count - 1) % comboBox.count; }
function nextItem() { root.currentHighLightedIndex = (root.currentHighLightedIndex + comboBox.count + 1) % comboBox.count; }

View file

@ -25,10 +25,16 @@ Original.RadioButton {
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
readonly property int boxSize: 14
readonly property int boxRadius: 3
readonly property int checkSize: 10
readonly property int checkRadius: 2
property real letterSpacing: 1
property int fontSize: hifi.fontSizes.inputLabel
property int boxSize: defaultBoxSize
property real scaleFactor: boxSize / defaultBoxSize
readonly property int defaultBoxSize: 14
readonly property int boxRadius: 3 * scaleFactor
readonly property int checkSize: 10 * scaleFactor
readonly property int checkRadius: 2 * scaleFactor
readonly property int indicatorRadius: 7 * scaleFactor
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
@ -44,7 +50,7 @@ Original.RadioButton {
id: box
width: boxSize
height: boxSize
radius: 7
radius: indicatorRadius
x: radioButton.leftPadding
y: parent.height / 2 - height / 2
gradient: Gradient {
@ -66,7 +72,7 @@ Original.RadioButton {
id: check
width: checkSize
height: checkSize
radius: 7
radius: indicatorRadius
anchors.centerIn: parent
color: "#00B4EF"
border.width: 1
@ -77,7 +83,8 @@ Original.RadioButton {
contentItem: RalewaySemiBold {
text: radioButton.text
size: hifi.fontSizes.inputLabel
size: radioButton.fontSize
font.letterSpacing: letterSpacing
color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter

View file

@ -27,6 +27,9 @@ SpinBox {
property string suffix: ""
property string labelInside: ""
property color colorLabelInside: hifi.colors.white
property color backgroundColor: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray)
: (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0)
property int decimals: 2;
property real factor: Math.pow(10, decimals)
@ -55,10 +58,14 @@ SpinBox {
onValueModified: realValue = value/factor
onValueChanged: realValue = value/factor
onRealValueChanged: {
var newValue = Math.round(realValue*factor);
if(value != newValue) {
value = newValue;
}
}
stepSize: realStepSize*factor
value: Math.round(realValue*factor)
to : realTo*factor
from : realFrom*factor
@ -69,9 +76,7 @@ SpinBox {
y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0
background: Rectangle {
color: isLightColorScheme
? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray)
: (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
color: backgroundColor
border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight
border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0
}

View file

@ -0,0 +1,76 @@
//
// Web3DOverlay.qml
//
// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtGraphicalEffects 1.0
Item {
property string url
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#262626" }
GradientStop { position: 1.0; color: "#000000" }
}
}
function shortUrl(url) {
var hostBegin = url.indexOf("://");
if (hostBegin > -1) {
url = url.substring(hostBegin + 3);
}
var portBegin = url.indexOf(":");
if (portBegin > -1) {
url = url.substring(0, portBegin);
}
var pathBegin = url.indexOf("/");
if (pathBegin > -1) {
url = url.substring(0, pathBegin);
}
if (url.length > 45) {
url = url.substring(0, 45);
}
return url;
}
Text {
id: urlText
text: shortUrl(url)
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.rightMargin: 10
anchors.leftMargin: 10
font.family: "Cairo"
font.weight: Font.DemiBold
font.pointSize: 48
fontSizeMode: Text.Fit
color: "#FFFFFF"
minimumPixelSize: 5
}
Image {
id: hand
source: "../../../icons/hand.svg"
width: 300
height: 300
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: 100
anchors.rightMargin: 100
}
}

View file

@ -26,11 +26,9 @@ Preference {
preference.save();
}
Item {
Row {
id: control
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: Math.max(spinnerLabel.height, spinner.controlHeight)
@ -40,15 +38,14 @@ Preference {
text: root.label + ":"
colorScheme: hifi.colorSchemes.dark
anchors {
left: parent.left
right: spinner.left
rightMargin: hifi.dimensions.labelPadding
verticalCenter: parent.verticalCenter
}
horizontalAlignment: Text.AlignRight
wrapMode: Text.Wrap
}
spacing: hifi.dimensions.labelPadding
SpinBox {
id: spinner
decimals: preference.decimals
@ -56,7 +53,6 @@ Preference {
maximumValue: preference.max
width: 100
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
colorScheme: hifi.colorSchemes.dark

View file

@ -0,0 +1,840 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQml.Models 2.1
import QtGraphicalEffects 1.0
import "../controls-uit" as HifiControls
import "../styles-uit"
import "avatarapp"
Rectangle {
id: root
width: 480
height: 706
property bool keyboardEnabled: true
property bool keyboardRaised: false
property bool punctuationMode: false
HifiControls.Keyboard {
id: keyboard
z: 1000
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
color: style.colors.white
property string getAvatarsMethod: 'getAvatars'
signal sendToScript(var message);
function emitSendToScript(message) {
sendToScript(message);
}
ListModel { // the only purpose of this model is to convert JS object to ListElement
id: currentAvatarModel
dynamicRoles: true;
function makeAvatarEntry(avatarObject) {
clear();
append(avatarObject);
return get(count - 1);
}
}
property var jointNames;
property var currentAvatarSettings;
function fetchAvatarModelName(marketId, avatar) {
var xmlhttp = new XMLHttpRequest();
var url = "https://highfidelity.com/api/v1/marketplace/items/" + marketId;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState === XMLHttpRequest.DONE && xmlhttp.status === 200) {
try {
var marketResponse = JSON.parse(xmlhttp.responseText.trim())
if(marketResponse.status === 'success') {
avatar.modelName = marketResponse.data.title;
}
}
catch(err) {
console.error(err);
}
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
function getAvatarModelName() {
if(currentAvatar === null) {
return '';
}
if(currentAvatar.modelName !== undefined) {
return currentAvatar.modelName;
} else {
var marketId = allAvatars.extractMarketId(currentAvatar.avatarUrl);
if(marketId !== '') {
fetchAvatarModelName(marketId, currentAvatar);
}
}
var avatarUrl = currentAvatar.avatarUrl;
var splitted = avatarUrl.split('/');
return splitted[splitted.length - 1];
}
property string avatarName: currentAvatar ? currentAvatar.name : ''
property string avatarUrl: currentAvatar ? currentAvatar.thumbnailUrl : null
property bool isAvatarInFavorites: currentAvatar ? allAvatars.findAvatar(currentAvatar.name) !== undefined : false
property int avatarWearablesCount: currentAvatar ? currentAvatar.wearables.count : 0
property var currentAvatar: null;
function setCurrentAvatar(avatar, bookmarkName) {
var currentAvatarObject = allAvatars.makeAvatarObject(avatar, bookmarkName);
currentAvatar = currentAvatarModel.makeAvatarEntry(currentAvatarObject);
}
property url externalAvatarThumbnailUrl: '../../images/avatarapp/guy-in-circle.svg'
function fromScript(message) {
if(message.method === 'initialize') {
jointNames = message.data.jointNames;
emitSendToScript({'method' : getAvatarsMethod});
} else if(message.method === 'wearableUpdated') {
adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties, message.updateUI);
} else if(message.method === 'wearablesUpdated') {
var wearablesModel = currentAvatar.wearables;
wearablesModel.clear();
message.wearables.forEach(function(wearable) {
wearablesModel.append(wearable);
});
adjustWearables.refresh(currentAvatar);
} else if(message.method === 'scaleChanged') {
currentAvatar.avatarScale = message.value;
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'externalAvatarApplied') {
currentAvatar.avatarUrl = message.avatarURL;
currentAvatar.thumbnailUrl = allAvatars.makeThumbnailUrl(message.avatarURL);
currentAvatar.entry.avatarUrl = currentAvatar.avatarUrl;
currentAvatar.modelName = undefined;
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'settingChanged') {
currentAvatarSettings[message.name] = message.value;
} else if(message.method === 'changeSettings') {
currentAvatarSettings = message.settings;
} else if(message.method === 'bookmarkLoaded') {
setCurrentAvatar(message.data.currentAvatar, message.data.name);
var avatarIndex = allAvatars.findAvatarIndex(currentAvatar.name);
allAvatars.move(avatarIndex, 0, 1);
view.setPage(0);
} else if(message.method === 'bookmarkAdded') {
var avatar = allAvatars.findAvatar(message.bookmarkName);
if(avatar !== undefined) {
var avatarObject = allAvatars.makeAvatarObject(message.bookmark, message.bookmarkName);
for(var prop in avatarObject) {
avatar[prop] = avatarObject[prop];
}
if(currentAvatar.name === message.bookmarkName) {
currentAvatar = currentAvatarModel.makeAvatarEntry(avatarObject);
}
} else {
allAvatars.addAvatarEntry(message.bookmark, message.bookmarkName);
}
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'bookmarkDeleted') {
pageOfAvatars.isUpdating = true;
var index = pageOfAvatars.findAvatarIndex(message.name);
var absoluteIndex = view.currentPage * view.itemsPerPage + index
allAvatars.remove(absoluteIndex)
pageOfAvatars.remove(index);
var itemsOnPage = pageOfAvatars.count;
var newItemIndex = view.currentPage * view.itemsPerPage + itemsOnPage;
if(newItemIndex <= (allAvatars.count - 1)) {
pageOfAvatars.append(allAvatars.get(newItemIndex));
} else {
if(!pageOfAvatars.hasGetAvatars())
pageOfAvatars.appendGetAvatars();
}
pageOfAvatars.isUpdating = false;
} else if(message.method === getAvatarsMethod) {
var getAvatarsData = message.data;
allAvatars.populate(getAvatarsData.bookmarks);
setCurrentAvatar(getAvatarsData.currentAvatar, '');
displayNameInput.text = getAvatarsData.displayName;
currentAvatarSettings = getAvatarsData.currentAvatarSettings;
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'updateAvatarInBookmarks') {
updateCurrentAvatarInBookmarks(currentAvatar);
} else if(message.method === 'selectAvatarEntity') {
adjustWearables.selectWearableByID(message.entityID);
}
}
function updateCurrentAvatarInBookmarks(avatar) {
var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(avatar);
if(bookmarkAvatarIndex === -1) {
avatar.name = '';
view.setPage(0);
} else {
var bookmarkAvatar = allAvatars.get(bookmarkAvatarIndex);
avatar.name = bookmarkAvatar.name;
view.selectAvatar(bookmarkAvatar);
}
}
property bool isInManageState: false
Component.onCompleted: {
}
AvatarAppStyle {
id: style
}
AvatarAppHeader {
id: header
z: 100
property string currentPage: "Avatar"
property bool mainPageVisible: !settings.visible && !adjustWearables.visible
Binding on currentPage {
when: settings.visible
value: "Avatar Settings"
}
Binding on currentPage {
when: adjustWearables.visible
value: "Adjust Wearables"
}
Binding on currentPage {
when: header.mainPageVisible
value: "Avatar"
}
pageTitle: currentPage
avatarIconVisible: mainPageVisible
settingsButtonVisible: mainPageVisible
onSettingsClicked: {
settings.open(currentAvatarSettings, currentAvatar.avatarScale);
}
}
Settings {
id: settings
anchors.left: parent.left
anchors.right: parent.right
anchors.top: header.bottom
anchors.bottom: parent.bottom
z: 3
onSaveClicked: function() {
var avatarSettings = {
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
collisionsEnabled : settings.avatarCollisionsOn,
animGraphUrl : settings.avatarAnimationJSON,
collisionSoundUrl : settings.avatarCollisionSoundUrl
};
emitSendToScript({'method' : 'saveSettings', 'settings' : avatarSettings, 'avatarScale': settings.scaleValue})
close();
}
onCancelClicked: function() {
emitSendToScript({'method' : 'revertScale', 'avatarScale' : avatarScaleBackup});
close();
}
onScaleChanged: {
emitSendToScript({'method' : 'setScale', 'avatarScale' : scale})
}
}
AdjustWearables {
id: adjustWearables
anchors.left: parent.left
anchors.right: parent.right
anchors.top: header.bottom
anchors.bottom: parent.bottom
jointNames: root.jointNames
onWearableUpdated: {
emitSendToScript({'method' : 'adjustWearable', 'entityID' : id, 'wearableIndex' : index, 'properties' : properties})
}
onWearableDeleted: {
emitSendToScript({'method' : 'deleteWearable', 'entityID' : id, 'avatarName' : avatarName});
}
onAdjustWearablesOpened: {
emitSendToScript({'method' : 'adjustWearablesOpened', 'avatarName' : avatarName});
}
onAdjustWearablesClosed: {
emitSendToScript({'method' : 'adjustWearablesClosed', 'save' : status, 'avatarName' : avatarName});
}
onWearableSelected: {
emitSendToScript({'method' : 'selectWearable', 'entityID' : id});
}
z: 3
}
Rectangle {
id: mainBlock
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right
anchors.rightMargin: 30
anchors.top: header.bottom
anchors.bottom: favoritesBlock.top
// TextStyle1
RalewaySemiBold {
size: 24;
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 34
}
// TextStyle1
RalewaySemiBold {
id: displayNameLabel
size: 24;
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 25
text: 'Display Name'
}
InputField {
id: displayNameInput
font.family: "Fira Sans"
font.pixelSize: 15
anchors.left: displayNameLabel.right
anchors.leftMargin: 30
anchors.verticalCenter: displayNameLabel.verticalCenter
anchors.right: parent.right
width: 232
text: 'ThisIsDisplayName'
onEditingFinished: {
emitSendToScript({'method' : 'changeDisplayName', 'displayName' : text})
focus = false;
}
}
ShadowImage {
id: avatarImage
width: 134
height: 134
anchors.top: displayNameLabel.bottom
anchors.topMargin: 31
Binding on source {
when: avatarUrl !== ''
value: avatarUrl
}
visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error
fillMode: Image.PreserveAspectCrop
}
ShadowImage {
id: customAvatarImage
anchors.fill: avatarImage;
visible: avatarUrl === '' || avatarImage.status === Image.Error
source: externalAvatarThumbnailUrl
}
ShadowRectangle {
anchors.fill: avatarImage;
color: 'white'
visible: avatarImage.status === Image.Loading
radius: avatarImage.radius
dropShadowRadius: avatarImage.dropShadowRadius;
dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
Spinner {
id: spinner
visible: parent.visible
anchors.fill: parent;
}
}
AvatarWearablesIndicator {
anchors.right: avatarImage.right
anchors.bottom: avatarImage.bottom
anchors.rightMargin: -radius
anchors.bottomMargin: 6.08
wearablesCount: avatarWearablesCount
visible: avatarWearablesCount !== 0
}
RowLayout {
id: star
anchors.top: avatarImage.top
anchors.topMargin: 11
anchors.left: avatarImage.right
anchors.leftMargin: 30.5
anchors.right: parent.right
spacing: 12.3
Image {
width: 21.2
height: 19.3
source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg'
anchors.verticalCenter: parent.verticalCenter
}
// TextStyle5
FiraSansSemiBold {
size: 22;
Layout.fillWidth: true
text: isAvatarInFavorites ? avatarName : "Add to Favorites"
elide: Qt.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
enabled: !isAvatarInFavorites
anchors.fill: star
onClicked: {
createFavorite.onSaveClicked = function() {
var entry = currentAvatar.entry;
var wearables = [];
for(var i = 0; i < currentAvatar.wearables.count; ++i) {
wearables.push(currentAvatar.wearables.get(i));
}
entry.avatarEntites = wearables;
currentAvatar.name = createFavorite.favoriteNameText;
emitSendToScript({'method': 'addAvatar', 'name' : currentAvatar.name});
createFavorite.close();
}
var avatarThumbnail = (avatarUrl === '' || avatarImage.status === Image.Error) ?
externalAvatarThumbnailUrl : avatarUrl;
createFavorite.open(root.currentAvatar.wearables.count, avatarThumbnail);
}
}
// TextStyle3
RalewayRegular {
id: avatarNameLabel
size: 22;
text: getAvatarModelName();
elide: Qt.ElideRight
anchors.right: linkLabel.left
anchors.left: avatarImage.right
anchors.leftMargin: 30
anchors.top: star.bottom
anchors.topMargin: 11
property bool hasMarketId: currentAvatar && allAvatars.extractMarketId(currentAvatar.avatarUrl) !== '';
MouseArea {
enabled: avatarNameLabel.hasMarketId
anchors.fill: parent;
onClicked: emitSendToScript({'method' : 'navigate', 'url' : allAvatars.makeMarketItemUrl(currentAvatar.avatarUrl)})
}
color: hasMarketId ? style.colors.blueHighlight : 'black'
}
// TextStyle3
RalewayRegular {
id: wearablesLabel
size: 22;
anchors.left: avatarImage.right
anchors.leftMargin: 30
anchors.top: avatarNameLabel.bottom
anchors.topMargin: 16
color: 'black'
text: 'Wearables'
}
SquareLabel {
id: linkLabel
anchors.right: parent.right
anchors.verticalCenter: avatarNameLabel.verticalCenter
glyphText: "."
glyphSize: 22
MouseArea {
anchors.fill: parent
onClicked: {
popup.showSpecifyAvatarUrl(function() {
var url = popup.inputText.text;
emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
}, function(link) {
Qt.openUrlExternally(link);
});
}
}
}
SquareLabel {
anchors.right: parent.right
anchors.verticalCenter: wearablesLabel.verticalCenter
glyphText: "\ue02e"
visible: avatarWearablesCount !== 0
MouseArea {
anchors.fill: parent
onClicked: {
adjustWearables.open(currentAvatar);
}
}
}
// TextStyle3
RalewayRegular {
size: 22;
anchors.right: parent.right
anchors.verticalCenter: wearablesLabel.verticalCenter
font.underline: true
text: "Add"
color: 'black'
visible: avatarWearablesCount === 0
MouseArea {
anchors.fill: parent
onClicked: {
popup.showGetWearables(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'})
}, function(link) {
emitSendToScript({'method' : 'navigate', 'url' : link})
});
}
}
}
}
Rectangle {
id: favoritesBlock
height: 407
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
color: style.colors.lightGrayBackground
// TextStyle1
RalewaySemiBold {
id: favoritesLabel
size: 24;
anchors.top: parent.top
anchors.topMargin: 15
anchors.left: parent.left
anchors.leftMargin: 30
text: "Favorites"
}
// TextStyle8
RalewaySemiBold {
id: manageLabel
color: style.colors.blueHighlight
size: 20;
anchors.top: parent.top
anchors.topMargin: 20
anchors.right: parent.right
anchors.rightMargin: 30
text: isInManageState ? "Back" : "Manage"
MouseArea {
anchors.fill: parent
onClicked: {
isInManageState = isInManageState ? false : true;
}
}
}
Item {
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: favoritesLabel.bottom
anchors.topMargin: 20
anchors.bottom: parent.bottom
GridView {
id: view
anchors.fill: parent
interactive: false;
currentIndex: currentAvatarIndexInBookmarksPage();
function currentAvatarIndexInBookmarksPage() {
return (currentAvatar && currentAvatar.name !== '' && !pageOfAvatars.isUpdating) ? pageOfAvatars.findAvatarIndex(currentAvatar.name) : -1;
}
property int horizontalSpacing: 18
property int verticalSpacing: 44
property int thumbnailWidth: 92
property int thumbnailHeight: 92
function selectAvatar(avatar) {
emitSendToScript({'method' : 'selectAvatar', 'name' : avatar.name})
}
function deleteAvatar(avatar) {
emitSendToScript({'method' : 'deleteAvatar', 'name' : avatar.name})
}
AvatarsModel {
id: allAvatars
}
property int itemsPerPage: 8
property int totalPages: Math.ceil((allAvatars.count + 1) / itemsPerPage)
property int currentPage: 0;
onCurrentPageChanged: {
currentIndex = Qt.binding(currentAvatarIndexInBookmarksPage);
}
property bool hasNext: currentPage < (totalPages - 1)
property bool hasPrev: currentPage > 0
function setPage(pageIndex) {
pageOfAvatars.isUpdating = true;
pageOfAvatars.clear();
var start = pageIndex * itemsPerPage;
var end = Math.min(start + itemsPerPage, allAvatars.count);
for(var itemIndex = 0; start < end; ++start, ++itemIndex) {
var avatarItem = allAvatars.get(start)
pageOfAvatars.append(avatarItem);
}
if(pageOfAvatars.count !== itemsPerPage)
pageOfAvatars.appendGetAvatars();
currentPage = pageIndex;
pageOfAvatars.isUpdating = false;
}
model: AvatarsModel {
id: pageOfAvatars
property bool isUpdating: false;
property var getMoreAvatarsEntry: {'thumbnailUrl' : '', 'name' : '', 'getMoreAvatars' : true}
function appendGetAvatars() {
append(getMoreAvatarsEntry);
}
function hasGetAvatars() {
return count != 0 && get(count - 1).getMoreAvatars
}
function removeGetAvatars() {
if(hasGetAvatars()) {
remove(count - 1)
}
}
}
flow: GridView.FlowLeftToRight
cellHeight: thumbnailHeight + verticalSpacing
cellWidth: thumbnailWidth + horizontalSpacing
delegate: Item {
id: delegateRoot
height: GridView.view.cellHeight
width: GridView.view.cellWidth
Item {
id: container
width: 92
height: 92
Behavior on y {
NumberAnimation {
duration: 100
}
}
states: [
State {
name: "hovered"
when: favoriteAvatarMouseArea.containsMouse;
PropertyChanges { target: favoriteAvatarMouseArea; anchors.bottomMargin: -5 }
PropertyChanges { target: container; y: -5 }
PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 }
PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 }
}
]
property bool highlighted: delegateRoot.GridView.isCurrentItem
AvatarThumbnail {
id: favoriteAvatarImage
externalAvatarThumbnailUrl: root.externalAvatarThumbnailUrl
avatarUrl: thumbnailUrl
border.color: container.highlighted ? style.colors.blueHighlight : 'transparent'
border.width: container.highlighted ? 4 : 0
wearablesCount: {
return !getMoreAvatars ? wearables.count : 0
}
visible: !getMoreAvatars
MouseArea {
id: favoriteAvatarMouseArea
anchors.fill: parent
anchors.margins: 0
enabled: !container.highlighted
hoverEnabled: enabled
onClicked: {
if(isInManageState) {
var currentItem = delegateRoot.GridView.view.model.get(index);
popup.showDeleteFavorite(currentItem.name, function() {
view.deleteAvatar(currentItem);
});
} else {
if(delegateRoot.GridView.view.currentIndex !== index) {
var currentItem = delegateRoot.GridView.view.model.get(index);
popup.showLoadFavorite(currentItem.name, function() {
view.selectAvatar(currentItem);
});
}
}
}
}
}
Rectangle {
anchors.fill: favoriteAvatarImage
color: '#AFAFAF'
opacity: 0.4
radius: 5
visible: isInManageState && !container.highlighted && !getMoreAvatars
}
HiFiGlyphs {
anchors.fill: parent
text: "{"
visible: isInManageState && !container.highlighted && !getMoreAvatars
horizontalAlignment: Text.AlignHCenter
size: 56
}
ShadowRectangle {
width: 92
height: 92
radius: 5
color: style.colors.blueHighlight
visible: getMoreAvatars && !isInManageState
HiFiGlyphs {
anchors.centerIn: parent
color: 'white'
size: 60
text: "K"
}
MouseArea {
anchors.fill: parent
onClicked: {
popup.showBuyAvatars(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://BodyMart'})
}, function(link) {
emitSendToScript({'method' : 'navigate', 'url' : link})
});
}
}
}
}
// TextStyle7
FiraSansRegular {
id: text
size: 18;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
width: view.thumbnailWidth
height: view.verticalSpacing
elide: Qt.ElideRight
anchors.top: container.bottom
anchors.topMargin: 8
anchors.horizontalCenter: container.horizontalCenter
verticalAlignment: Text.AlignTop
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: getMoreAvatars ? 'Get More Avatars' : name
visible: !getMoreAvatars || !isInManageState
}
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: 40
height: 40
color: 'transparent'
PageIndicator {
x: 1
hasNext: view.hasNext
hasPrev: view.hasPrev
onClicked: view.setPage(view.currentPage - 1)
}
}
spacing: 0
Rectangle {
width: 40
height: 40
color: 'transparent'
PageIndicator {
x: -1
isPrevious: false
hasNext: view.hasNext
hasPrev: view.hasPrev
onClicked: view.setPage(view.currentPage + 1)
}
}
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
}
}
MessageBoxes {
id: popup
}
CreateFavoriteDialog {
avatars: allAvatars
id: createFavorite
}
}

View file

@ -765,7 +765,7 @@ Rectangle {
TableViewColumn {
id: connectionsUserNameHeader;
role: "userName";
title: connectionsTable.rowCount + (connectionsTable.rowCount === 1 ? " NAME" : " NAMES");
title: connectionsUserModel.totalEntries + (connectionsUserModel.totalEntries === 1 ? " NAME" : " NAMES");
width: connectionsNameCardWidth;
movable: false;
resizable: false;

View file

@ -0,0 +1,359 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
width: 480
height: 706
color: 'white'
signal wearableUpdated(var id, int index, var properties);
signal wearableSelected(var id);
signal wearableDeleted(string avatarName, var id);
signal adjustWearablesOpened(var avatarName);
signal adjustWearablesClosed(bool status, var avatarName);
property bool modified: false;
Component.onCompleted: {
modified = false;
}
property var jointNames;
property string avatarName: ''
property var wearablesModel;
function open(avatar) {
adjustWearablesOpened(avatar.name);
visible = true;
avatarName = avatar.name;
wearablesModel = avatar.wearables;
refresh(avatar);
}
function refresh(avatar) {
wearablesCombobox.model.clear();
for(var i = 0; i < avatar.wearables.count; ++i) {
var wearable = avatar.wearables.get(i).properties;
for(var j = (wearable.modelURL.length - 1); j >= 0; --j) {
if(wearable.modelURL[j] === '/') {
wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]);
break;
}
}
wearablesCombobox.model.append(wearable);
}
wearablesCombobox.currentIndex = 0;
}
function refreshWearable(wearableID, wearableIndex, properties, updateUI) {
if(wearableIndex === -1) {
wearableIndex = wearablesCombobox.model.findIndexById(wearableID);
}
var wearable = wearablesCombobox.model.get(wearableIndex);
if(!wearable) {
return;
}
var wearableModelItemProperties = wearablesModel.get(wearableIndex).properties;
for(var prop in properties) {
wearable[prop] = properties[prop];
wearableModelItemProperties[prop] = wearable[prop];
if(updateUI) {
if(prop === 'localPosition') {
position.set(wearable[prop]);
} else if(prop === 'localRotationAngles') {
rotation.set(wearable[prop]);
} else if(prop === 'dimensions') {
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
}
}
}
wearablesModel.setProperty(wearableIndex, 'properties', wearableModelItemProperties);
}
function getCurrentWearable() {
return wearablesCombobox.model.get(wearablesCombobox.currentIndex)
}
function selectWearableByID(entityID) {
for(var i = 0; i < wearablesCombobox.model.count; ++i) {
var wearable = wearablesCombobox.model.get(i);
if(wearable.id === entityID) {
wearablesCombobox.currentIndex = i;
break;
}
}
}
function close(status) {
visible = false;
adjustWearablesClosed(status, avatarName);
}
HifiConstants { id: hifi }
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Column {
anchors.top: parent.top
anchors.topMargin: 15
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20
width: parent.width - 30 * 2
HifiControlsUit.ComboBox {
id: wearablesCombobox
anchors.left: parent.left
anchors.right: parent.right
comboBox.textRole: "text"
model: ListModel {
function findIndexById(id) {
for(var i = 0; i < count; ++i) {
var wearable = get(i);
if(wearable.id === id) {
return i;
}
}
return -1;
}
}
comboBox.onCurrentIndexChanged: {
var currentWearable = getCurrentWearable();
if(currentWearable) {
position.set(currentWearable.localPosition);
rotation.set(currentWearable.localRotationAngles);
scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x)
wearableSelected(currentWearable.id);
}
}
}
Column {
width: parent.width
spacing: 5
Row {
spacing: 20
// TextStyle5
FiraSansSemiBold {
id: positionLabel
size: 22;
text: "Position"
}
// TextStyle7
FiraSansRegular {
size: 18;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
text: "m"
anchors.verticalCenter: positionLabel.verticalCenter
}
}
Vector3 {
id: position
backgroundColor: "lightgray"
function set(localPosition) {
notify = false;
xvalue = localPosition.x
yvalue = localPosition.y
zvalue = localPosition.z
notify = true;
}
function notifyPositionChanged() {
modified = true;
var properties = {
localPosition: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
};
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
onXvalueChanged: if(notify) notifyPositionChanged();
onYvalueChanged: if(notify) notifyPositionChanged();
onZvalueChanged: if(notify) notifyPositionChanged();
decimals: 2
realFrom: -10
realTo: 10
realStepSize: 0.01
}
}
Column {
width: parent.width
spacing: 5
Row {
spacing: 20
// TextStyle5
FiraSansSemiBold {
id: rotationLabel
size: 22;
text: "Rotation"
}
// TextStyle7
FiraSansRegular {
size: 18;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
text: "deg"
anchors.verticalCenter: rotationLabel.verticalCenter
}
}
Vector3 {
id: rotation
backgroundColor: "lightgray"
function set(localRotationAngles) {
notify = false;
xvalue = localRotationAngles.x
yvalue = localRotationAngles.y
zvalue = localRotationAngles.z
notify = true;
}
function notifyRotationChanged() {
modified = true;
var properties = {
localRotationAngles: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
};
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
onXvalueChanged: if(notify) notifyRotationChanged();
onYvalueChanged: if(notify) notifyRotationChanged();
onZvalueChanged: if(notify) notifyRotationChanged();
decimals: 0
realFrom: -180
realTo: 180
realStepSize: 1
}
}
Column {
width: parent.width
spacing: 5
// TextStyle5
FiraSansSemiBold {
size: 22;
text: "Scale"
}
Item {
width: parent.width
height: childrenRect.height
HifiControlsUit.SpinBox {
id: scalespinner
decimals: 2
realStepSize: 0.1
realFrom: 0.1
realTo: 3.0
realValue: 1.0
backgroundColor: "lightgray"
width: position.spinboxWidth
colorScheme: hifi.colorSchemes.light
property bool notify: false;
onValueChanged: if(notify) notifyScaleChanged();
function set(value) {
notify = false;
realValue = value
notify = true;
}
function notifyScaleChanged() {
modified = true;
var currentWearable = getCurrentWearable();
var naturalDimensions = currentWearable.naturalDimensions;
var properties = {
dimensions: {
'x' : realValue * naturalDimensions.x,
'y' : realValue * naturalDimensions.y,
'z' : realValue * naturalDimensions.z
}
};
wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties);
}
}
HifiControlsUit.Button {
fontSize: 18
height: 40
anchors.right: parent.right
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.dark;
text: "TAKE IT OFF"
onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id);
enabled: wearablesCombobox.model.count !== 0
anchors.verticalCenter: scalespinner.verticalCenter
}
}
}
}
DialogButtons {
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right
anchors.rightMargin: 30
yesText: "SAVE"
noText: "CANCEL"
onYesClicked: function() {
root.close(true);
}
onNoClicked: function() {
root.close(false);
}
}
}

View file

@ -0,0 +1,58 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
ShadowRectangle {
id: header
anchors.left: parent.left
anchors.right: parent.right
height: 60
property alias pageTitle: title.text
property alias avatarIconVisible: avatarIcon.visible
property alias settingsButtonVisible: settingsButton.visible
signal settingsClicked;
AvatarAppStyle {
id: style
}
color: style.colors.lightGrayBackground
HiFiGlyphs {
id: avatarIcon
anchors.left: parent.left
anchors.leftMargin: 23
anchors.verticalCenter: header.verticalCenter
size: 38
text: "<"
}
// TextStyle6
RalewaySemiBold {
id: title
size: 22;
anchors.left: avatarIcon.visible ? avatarIcon.right : avatarIcon.left
anchors.leftMargin: 4
anchors.verticalCenter: avatarIcon.verticalCenter
text: 'Avatar'
}
HiFiGlyphs {
id: settingsButton
anchors.right: parent.right
anchors.rightMargin: 30
anchors.verticalCenter: avatarIcon.verticalCenter
text: "&"
MouseArea {
id: settingsMouseArea
anchors.fill: parent
onClicked: {
settingsClicked();
}
}
}
}

View file

@ -0,0 +1,29 @@
//
// HiFiConstants.qml
//
// Created by Alexander Ivash on 17 Apr 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
//
import QtQuick 2.5
import QtQuick.Window 2.2
import "../../styles-uit"
QtObject {
readonly property QtObject colors: QtObject {
readonly property color lightGrayBackground: "#f2f2f2"
readonly property color black: "#000000"
readonly property color white: "#ffffff"
readonly property color blueHighlight: "#00b4ef"
readonly property color inputFieldBackground: "#d4d4d4"
readonly property color yellowishOrange: "#ffb017"
readonly property color blueAccent: "#0093c5"
readonly property color greenHighlight: "#1fc6a6"
readonly property color lightGray: "#afafaf"
readonly property color redHighlight: "#ea4c5f"
readonly property color orangeAccent: "#ff6309"
}
}

View file

@ -0,0 +1,73 @@
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
width: 92
height: 92
property alias wearableIndicator: indicator
property int wearablesCount: 0
onWearablesCountChanged: {
console.debug('AvatarThumbnail: wearablesCount = ', wearablesCount)
}
property alias dropShadowRadius: avatarImage.dropShadowRadius
property alias dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
property alias dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
property url externalAvatarThumbnailUrl;
property var avatarUrl;
property alias border: avatarImage.border
ShadowImage {
id: avatarImage
anchors.fill: parent
radius: 5
fillMode: Image.PreserveAspectCrop
Binding on source {
when: avatarUrl !== ''
value: avatarUrl
}
onSourceChanged: {
console.debug('avatarImage: source = ', source);
}
visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error
}
ShadowImage {
id: customAvatarImage
anchors.fill: avatarImage;
visible: avatarUrl === '' || avatarImage.status === Image.Error
source: externalAvatarThumbnailUrl
}
ShadowRectangle {
anchors.fill: parent;
color: 'white'
visible: avatarImage.status === Image.Loading
radius: avatarImage.radius
border.width: avatarImage.border.width
border.color: avatarImage.border.color
dropShadowRadius: avatarImage.dropShadowRadius;
dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
Spinner {
id: spinner
visible: parent.visible
anchors.fill: parent;
}
}
AvatarWearablesIndicator {
id: indicator
anchors.left: avatarImage.left
anchors.bottom: avatarImage.bottom
anchors.leftMargin: 57
wearablesCount: parent.wearablesCount
visible: parent.wearablesCount !== 0
}
}

View file

@ -0,0 +1,46 @@
import QtQuick 2.9
import "../../controls-uit"
import "../../styles-uit"
ShadowRectangle {
property int wearablesCount: 0
dropShadowRadius: 4
dropShadowHorizontalOffset: 0
dropShadowVerticalOffset: 0
width: 46.5
height: 46.5
radius: width / 2
AvatarAppStyle {
id: style
}
color: style.colors.greenHighlight
HiFiGlyphs {
width: 26.5
height: 13.8
anchors.top: parent.top
anchors.topMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
text: "\ue02e"
}
Item {
width: 46.57
height: 23
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 2.76
// TextStyle2
RalewayBold {
size: 15;
anchors.horizontalCenter: parent.horizontalCenter
text: wearablesCount
}
}
}

View file

@ -0,0 +1,228 @@
import QtQuick 2.9
ListModel {
id: model
function extractMarketId(avatarUrl) {
var guidRegexp = '([A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12})';
var regexp = new RegExp(guidRegexp,["i"]);
var match = regexp.exec(avatarUrl);
if (match !== null) {
return match[1];
}
return '';
}
function makeMarketItemUrl(avatarUrl) {
var marketItemUrl = "https://highfidelity.com/marketplace/items/%marketId%"
.split('%marketId%').join(extractMarketId(avatarUrl));
return marketItemUrl;
}
function makeThumbnailUrl(avatarUrl) {
var marketId = extractMarketId(avatarUrl);
if(marketId === '')
return '';
var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg"
.split('%marketId%').join(marketId);
return avatarThumbnailUrl;
}
function makeAvatarObject(avatar, avatarName) {
var avatarThumbnailUrl = makeThumbnailUrl(avatar.avatarUrl);
return {
'name' : avatarName,
'avatarScale' : avatar.avatarScale,
'thumbnailUrl' : avatarThumbnailUrl,
'avatarUrl' : avatar.avatarUrl,
'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [],
'attachments' : avatar.attachments ? avatar.attachments : [],
'entry' : avatar,
'getMoreAvatars' : false
};
}
function addAvatarEntry(avatar, avatarName) {
var avatarEntry = makeAvatarObject(avatar, avatarName);
append(avatarEntry);
return allAvatars.count - 1;
}
function populate(bookmarks) {
clear();
for(var avatarName in bookmarks) {
var avatar = bookmarks[avatarName];
var avatarEntry = makeAvatarObject(avatar, avatarName);
append(avatarEntry);
}
}
function arraysAreEqual(a1, a2, comparer) {
if(Array.isArray(a1) && Array.isArray(a2)) {
if(a1.length !== a2.length) {
return false;
}
for(var i = 0; i < a1.length; ++i) {
if(!comparer(a1[i], a2[i])) {
return false;
}
}
} else if(Array.isArray(a1)) {
return a1.length === 0;
} else if(Array.isArray(a2)) {
return a2.length === 0;
}
return true;
}
function modelsAreEqual(m1, m2, comparer) {
if(m1.count !== m2.count) {
return false;
}
for(var i = 0; i < m1.count; ++i) {
var e1 = m1.get(i);
var allDifferent = true;
// it turns out order of wearables can randomly change so make position-independent comparison here
for(var j = 0; j < m2.count; ++j) {
var e2 = m2.get(j);
if(comparer(e1, e2)) {
allDifferent = false;
break;
}
}
if(allDifferent) {
return false;
}
}
return true;
}
function compareNumericObjects(o1, o2) {
if(o1 === undefined && o2 !== undefined)
return false;
if(o1 !== undefined && o2 === undefined)
return false;
for(var prop in o1) {
if(o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) {
var v1 = o1[prop];
var v2 = o2[prop];
if(v1 !== v2 && Math.round(v1 * 500) != Math.round(v2 * 500)) {
return false;
}
}
}
return true;
}
function compareObjects(o1, o2, props, arrayProp) {
for(var i = 0; i < props.length; ++i) {
var prop = props[i];
var propertyName = prop.propertyName;
var comparer = prop.comparer;
var o1Value = arrayProp ? o1[arrayProp][propertyName] : o1[propertyName];
var o2Value = arrayProp ? o2[arrayProp][propertyName] : o2[propertyName];
if(comparer) {
if(comparer(o1Value, o2Value) === false) {
return false;
}
} else {
if(JSON.stringify(o1Value) !== JSON.stringify(o2Value)) {
return false;
}
}
}
return true;
}
function compareWearables(w1, w2) {
return compareObjects(w1, w2, [{'propertyName' : 'modelURL'},
{'propertyName' : 'parentJointIndex'},
{'propertyName' : 'marketplaceID'},
{'propertyName' : 'itemName'},
{'propertyName' : 'script'},
{'propertyName' : 'localPosition', 'comparer' : compareNumericObjects},
{'propertyName' : 'localRotationAngles', 'comparer' : compareNumericObjects},
{'propertyName' : 'dimensions', 'comparer' : compareNumericObjects}], 'properties')
}
function compareAttachments(a1, a2) {
return compareObjects(a1, a2, [{'propertyName' : 'position', 'comparer' : compareNumericObjects},
{'propertyName' : 'orientation'},
{'propertyName' : 'parentJointIndex'},
{'propertyName' : 'modelurl'}])
}
function findAvatarIndexByValue(avatar) {
var index = -1;
// 2DO: find better way of determining selected avatar in bookmarks
for(var i = 0; i < allAvatars.count; ++i) {
var thesame = true;
var bookmarkedAvatar = allAvatars.get(i);
if(bookmarkedAvatar.avatarUrl !== avatar.avatarUrl)
continue;
if(bookmarkedAvatar.avatarScale !== avatar.avatarScale)
continue;
if(!modelsAreEqual(bookmarkedAvatar.attachments, avatar.attachments, compareAttachments)) {
continue;
}
if(!modelsAreEqual(bookmarkedAvatar.wearables, avatar.wearables, compareWearables)) {
continue;
}
if(thesame) {
index = i;
break;
}
}
return index;
}
function findAvatarIndex(avatarName) {
for(var i = 0; i < count; ++i) {
if(get(i).name === avatarName) {
return i;
}
}
return -1;
}
function findAvatar(avatarName) {
var avatarIndex = findAvatarIndex(avatarName);
if(avatarIndex === -1)
return undefined;
return get(avatarIndex);
}
}

View file

@ -0,0 +1,15 @@
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
HifiControlsUit.Button {
HifiConstants {
id: hifi
}
width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20)
fontSize: 18
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
height: 40
}

View file

@ -0,0 +1,184 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
anchors.fill: parent;
color: Qt.rgba(0, 0, 0, 0.5);
z: 999;
property string titleText: 'Create Favorite'
property string favoriteNameText: favoriteName.text
property string avatarImageUrl: null
property int wearablesCount: 0
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text: 'CANCEL'
property string button2color: hifi.buttons.blue;
property string button2text: 'CONFIRM'
property var avatars;
property var onSaveClicked;
property var onCancelClicked;
function open(wearables, thumbnail) {
favoriteName.text = '';
favoriteName.forceActiveFocus();
avatarImageUrl = thumbnail;
wearablesCount = wearables;
visible = true;
}
function close() {
console.debug('closing');
visible = false;
}
HifiConstants {
id: hifi
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Rectangle {
id: mainContainer;
width: Math.max(parent.width * 0.8, 400)
property int margin: 30;
height: childrenRect.height + margin * 2
onHeightChanged: {
console.debug('mainContainer: height = ', height)
}
anchors.centerIn: parent
color: "white"
// TextStyle1
RalewaySemiBold {
id: title
size: 24;
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 30
anchors.leftMargin: 30
anchors.rightMargin: 30
text: root.titleText
}
Item {
id: contentContainer
width: parent.width - 50
height: childrenRect.height
anchors.top: title.bottom
anchors.topMargin: 20
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
AvatarThumbnail {
id: avatarThumbnail
avatarUrl: avatarImageUrl
onAvatarUrlChanged: {
console.debug('CreateFavoritesDialog: onAvatarUrlChanged: ', avatarUrl);
}
wearablesCount: avatarWearablesCount
}
InputTextStyle4 {
id: favoriteName
anchors.right: parent.right
height: 40
anchors.left: avatarThumbnail.right
anchors.leftMargin: 44
anchors.verticalCenter: avatarThumbnail.verticalCenter
placeholderText: "Enter Favorite Name"
RalewayRegular {
id: wrongName
anchors.top: parent.bottom;
anchors.topMargin: 2
anchors.left: parent.left
anchors.right: parent.right;
anchors.rightMargin: -contentContainer.anchors.rightMargin // allow text to render beyond favorite input text
wrapMode: Text.WordWrap
text: 'Favorite name exists. Overwrite existing favorite?'
size: 15
color: 'red'
visible: {
for(var i = 0; i < avatars.count; ++i) {
var avatarName = avatars.get(i).name;
if(avatarName === favoriteName.text) {
return true;
}
}
return false;
}
}
}
}
DialogButtons {
anchors.top: contentContainer.bottom
anchors.topMargin: 20
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 30
yesButton.enabled: favoriteNameText !== ''
yesText: root.button2text
noText: root.button1text
Binding on yesButton.text {
when: wrongName.visible
value: "OVERWRITE";
}
Binding on yesButton.color {
when: wrongName.visible
value: hifi.buttons.red;
}
Binding on yesButton.colorScheme {
when: wrongName.visible
value: hifi.colorSchemes.dark;
}
onYesClicked: function() {
if(onSaveClicked) {
onSaveClicked();
} else {
root.close();
}
}
onNoClicked: function() {
if(onCancelClicked) {
onCancelClicked();
} else {
root.close();
}
}
}
}
}

View file

@ -0,0 +1,41 @@
import QtQuick 2.9
Row {
id: root
property string yesText;
property string noText;
property var onYesClicked;
property var onNoClicked;
property alias yesButton: yesButton
property alias noButton: noButton
height: childrenRect.height
layoutDirection: Qt.RightToLeft
spacing: 30
BlueButton {
id: yesButton;
text: yesText;
onClicked: {
console.debug('bluebutton.clicked', onYesClicked);
if(onYesClicked) {
onYesClicked();
}
}
}
WhiteButton {
id: noButton
text: noText;
onClicked: {
console.debug('whitebutton.clicked', onNoClicked);
if(onNoClicked) {
onNoClicked();
}
}
}
}

View file

@ -0,0 +1,48 @@
import QtQuick 2.5
import QtQuick.Controls 2.2
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
TextField {
id: textField
property bool error: false;
text: 'ThisIsDisplayName'
states: [
State {
name: "hovered"
when: textField.hovered && !textField.focus && !textField.error;
PropertyChanges { target: background; color: '#afafaf' }
},
State {
name: "focused"
when: textField.focus && !textField.error
PropertyChanges { target: background; color: '#f2f2f2' }
PropertyChanges { target: background; border.color: '#00b4ef' }
},
State {
name: "error"
when: textField.error
PropertyChanges { target: background; color: '#f2f2f2' }
PropertyChanges { target: background; border.color: '#e84e62' }
}
]
background: Rectangle {
id: background
implicitWidth: 200
implicitHeight: 40
color: '#d4d4d4'
border.color: '#afafaf'
border.width: 1
radius: 2
}
HiFiGlyphs {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
size: 36
text: "\ue00d"
}
}

View file

@ -0,0 +1,16 @@
import "../../controls-uit" as HifiControlsUit
import "../../styles-uit"
import QtQuick 2.0
import QtQuick.Controls 2.2
HifiControlsUit.TextField {
id: control
font.family: "Fira Sans"
font.pixelSize: 15;
implicitHeight: 40
AvatarAppStyle {
id: style
}
}

View file

@ -0,0 +1,184 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
anchors.fill: parent;
color: Qt.rgba(0, 0, 0, 0.5);
z: 999;
property string titleText: ''
property string bodyText: ''
property alias inputText: input;
property string imageSource: null
onImageSourceChanged: {
console.debug('imageSource = ', imageSource)
}
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text: ''
property string button2color: hifi.buttons.blue;
property string button2text: ''
property var onButton2Clicked;
property var onButton1Clicked;
property var onLinkClicked;
function open() {
visible = true;
}
function close() {
visible = false;
onButton1Clicked = null;
onButton2Clicked = null;
button1text = '';
button2text = '';
imageSource = null;
inputText.visible = false;
inputText.placeholderText = '';
inputText.text = '';
}
HifiConstants {
id: hifi
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Rectangle {
id: mainContainer;
width: Math.max(parent.width * 0.8, 400)
property int margin: 30;
height: childrenRect.height + margin * 2
onHeightChanged: {
console.debug('mainContainer: height = ', height)
}
anchors.centerIn: parent
color: "white"
// TextStyle1
RalewaySemiBold {
id: title
size: 24;
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 30
anchors.leftMargin: 30
anchors.rightMargin: 30
text: root.titleText
elide: Qt.ElideRight
}
Column {
id: contentContainer
spacing: 15
anchors.top: title.bottom
anchors.topMargin: 10
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
InputTextStyle4 {
id: input
visible: false
height: visible ? implicitHeight : 0
anchors.left: parent.left;
anchors.right: parent.right;
}
// TextStyle3
RalewayRegular {
id: body
AvatarAppStyle {
id: style
}
size: 18
text: root.bodyText;
linkColor: style.colors.blueHighlight
anchors.left: parent.left;
anchors.right: parent.right;
height: paintedHeight;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
onLinkActivated: {
if(onLinkClicked)
onLinkClicked(link);
}
}
Image {
id: image
Binding on height {
when: imageSource === null
value: 0
}
anchors.left: parent.left;
anchors.right: parent.right;
Binding on source {
when: imageSource !== null
value: imageSource
}
visible: imageSource !== null ? true : false
}
}
DialogButtons {
id: buttons
anchors.top: contentContainer.bottom
anchors.topMargin: 30
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 30
yesButton.enabled: !input.visible || input.text.length !== 0
yesText: root.button2text
noText: root.button1text
onYesClicked: function() {
if(onButton2Clicked) {
onButton2Clicked();
} else {
root.close();
}
}
onNoClicked: function() {
if(onButton1Clicked) {
onButton1Clicked();
} else {
root.close();
}
}
}
}
}

View file

@ -0,0 +1,122 @@
import QtQuick 2.5
MessageBox {
id: popup
function showSpecifyAvatarUrl(callback, linkCallback) {
popup.onButton2Clicked = callback;
popup.titleText = 'Specify Avatar URL'
popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.<br>' +
'<a href="https://docs.highfidelity.com/create-and-explore/avatars/create-avatars">' +
'Learn to make a custom avatar by opening this link on your desktop.' +
'</a>'
popup.inputText.visible = true;
popup.inputText.placeholderText = 'Enter Avatar Url';
popup.button1text = 'CANCEL';
popup.button2text = 'CONFIRM';
popup.onButton2Clicked = function() {
if(callback)
callback();
popup.close();
}
popup.onLinkClicked = function(link) {
if(linkCallback)
linkCallback(link);
}
popup.open();
popup.inputText.forceActiveFocus();
}
property url getWearablesUrl: '../../../images/avatarapp/AvatarIsland.jpg'
function showGetWearables(callback, linkCallback) {
popup.button2text = 'AvatarIsland'
popup.button1text = 'CANCEL'
popup.titleText = 'Get Wearables'
popup.bodyText = 'Buy wearables from <a href="app://marketplace">Marketplace</a>' + '<br/>' +
'Wear wearables from <a href="app://purchases">My Purchases</a>' + '<br/>' +
'You can visit the domain “AvatarIsland” to get wearables'
popup.imageSource = getWearablesUrl;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.onLinkClicked = function(link) {
popup.close();
if(linkCallback)
linkCallback(link);
}
popup.open();
}
function showDeleteFavorite(favoriteName, callback) {
popup.titleText = 'Delete Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName)
popup.bodyText = 'This will delete your favorite. You will retain access to the wearables and avatar that made up the favorite from My Purchases.'
popup.imageSource = null;
popup.button1text = 'CANCEL'
popup.button2text = 'DELETE'
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.open();
}
function showLoadFavorite(favoriteName, callback) {
popup.button2text = 'CONFIRM'
popup.button1text = 'CANCEL'
popup.titleText = 'Load Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName)
popup.bodyText = 'This will switch your current avatar and wearables that you are wearing with a new avatar and wearables.'
popup.imageSource = null;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.open();
}
property url getAvatarsUrl: '../../../images/avatarapp/BodyMart.PNG'
function showBuyAvatars(callback, linkCallback) {
popup.button2text = 'BodyMart'
popup.button1text = 'CANCEL'
popup.titleText = 'Get Avatars'
popup.bodyText = 'Buy avatars from <a href="app://marketplace">Marketplace</a>' + '<br/>' +
'Wear avatars from <a href="app://purchases">My Purchases</a>' + '<br/>' +
'You can visit the domain “BodyMart” to get avatars'
popup.imageSource = getAvatarsUrl;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
callback();
}
popup.onLinkClicked = function(link) {
popup.close();
if(linkCallback)
linkCallback(link);
}
popup.open();
}
}

View file

@ -0,0 +1,44 @@
import QtQuick 2.9
ShadowGlyph {
id: indicator
property bool isPrevious: true;
property bool hasNext: false
property bool hasPrev: false
property bool isEnabled: isPrevious ? hasPrev : hasNext
signal clicked;
states: [
State {
name: "hovered"
when: pageIndicatorMouseArea.containsMouse;
PropertyChanges { target: pageIndicatorMouseArea; anchors.bottomMargin: -5 }
PropertyChanges { target: indicator; y: -5 }
PropertyChanges { target: indicator; dropShadowVerticalOffset: 9 }
}
]
Behavior on y {
NumberAnimation {
duration: 100
}
}
text: isPrevious ? "E" : "D";
width: 40
height: 40
font.pixelSize: 100
color: isEnabled ? 'black' : 'gray'
visible: hasNext || hasPrev
horizontalAlignment: Text.AlignHCenter
MouseArea {
id: pageIndicatorMouseArea
anchors.fill: parent
enabled: isEnabled
hoverEnabled: enabled
onClicked: {
parent.clicked();
}
}
}

View file

@ -0,0 +1,36 @@
import QtQuick 2.0
Item {
property alias border: borderRectangle.border
property alias source: image.source
property alias fillMode: image.fillMode
property alias radius: mask.radius
property alias status: image.status
property alias progress: image.progress
Image {
id: image
anchors.fill: parent
anchors.margins: borderRectangle.border.width
}
Rectangle {
id: mask
anchors.fill: image
}
TransparencyMask {
anchors.fill: image
source: image
maskSource: mask
}
Rectangle {
id: borderRectangle
anchors.fill: parent
radius: mask.radius
color: "transparent"
}
}

View file

@ -0,0 +1,353 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root
color: 'white'
visible: false;
signal scaleChanged(real scale);
property alias onSaveClicked: dialogButtons.onYesClicked
property alias onCancelClicked: dialogButtons.onNoClicked
property real scaleValue: scaleSlider.value / 10
property alias dominantHandIsLeft: leftHandRadioButton.checked
property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked
property alias avatarAnimationJSON: avatarAnimationUrlInputText.text
property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
property real avatarScaleBackup;
function open(settings, avatarScale) {
console.debug('Settings.qml: open: ', JSON.stringify(settings, 0, 4));
avatarScaleBackup = avatarScale;
scaleSlider.notify = false;
scaleSlider.value = Math.round(avatarScale * 10);
scaleSlider.notify = true;;
if(settings.dominantHand === 'left') {
leftHandRadioButton.checked = true;
} else {
rightHandRadioButton.checked = true;
}
if(settings.collisionsEnabled) {
collisionsEnabledRadiobutton.checked = true;
} else {
collisionsDisabledRadioButton.checked = true;
}
avatarAnimationJSON = settings.animGraphUrl;
avatarCollisionSoundUrl = settings.collisionSoundUrl;
visible = true;
}
function close() {
visible = false
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
}
Item {
anchors.left: parent.left
anchors.leftMargin: 27
anchors.top: parent.top
anchors.topMargin: 25
anchors.right: parent.right
anchors.rightMargin: 32
anchors.bottom: parent.bottom
anchors.bottomMargin: 57
RowLayout {
id: avatarScaleRow
anchors.left: parent.left
anchors.right: parent.right
spacing: 17
// TextStyle9
RalewaySemiBold {
size: 17;
text: "Avatar Scale"
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
RowLayout {
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
spacing: 0
HiFiGlyphs {
size: 30
text: 'T'
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
HifiControlsUit.Slider {
id: scaleSlider
property bool notify: false;
from: 1
to: 40
onValueChanged: {
console.debug('value changed: ', value);
if(notify) {
console.debug('notifying.. ');
root.scaleChanged(value / 10);
}
}
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
// TextStyle9
RalewaySemiBold {
size: 17;
anchors.left: scaleSlider.left
anchors.leftMargin: 5
anchors.top: scaleSlider.bottom
anchors.topMargin: 2
text: String(scaleSlider.from / 10) + 'x'
}
// TextStyle9
RalewaySemiBold {
size: 17;
anchors.right: scaleSlider.right
anchors.rightMargin: 5
anchors.top: scaleSlider.bottom
anchors.topMargin: 2
text: String(scaleSlider.to / 10) + 'x'
}
}
HiFiGlyphs {
size: 40
text: 'T'
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
}
ShadowRectangle {
width: 37
height: 28
AvatarAppStyle {
id: style
}
gradient: Gradient {
GradientStop { position: 0.0; color: style.colors.blueHighlight }
GradientStop { position: 1.0; color: style.colors.blueAccent }
}
radius: 3
RalewaySemiBold {
color: 'white'
anchors.centerIn: parent
text: "1x"
size: 18
}
MouseArea {
anchors.fill: parent
onClicked: {
scaleSlider.value = 10
}
}
}
}
GridLayout {
id: handAndCollisions
anchors.top: avatarScaleRow.bottom
anchors.topMargin: 39
anchors.left: parent.left
anchors.right: parent.right
rows: 2
rowSpacing: 25
columns: 3
// TextStyle9
RalewaySemiBold {
size: 17;
Layout.row: 0
Layout.column: 0
text: "Dominant Hand"
}
ButtonGroup {
id: leftRight
}
HifiControlsUit.RadioButton {
id: leftHandRadioButton
Layout.row: 0
Layout.column: 1
Layout.leftMargin: -40
ButtonGroup.group: leftRight
checked: true
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Left"
boxSize: 20
}
HifiControlsUit.RadioButton {
id: rightHandRadioButton
Layout.row: 0
Layout.column: 2
Layout.rightMargin: 20
ButtonGroup.group: leftRight
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "Right"
boxSize: 20
}
// TextStyle9
RalewaySemiBold {
size: 17;
Layout.row: 1
Layout.column: 0
text: "Avatar Collisions"
}
ButtonGroup {
id: onOff
}
HifiControlsUit.RadioButton {
id: collisionsEnabledRadiobutton
Layout.row: 1
Layout.column: 1
Layout.leftMargin: -40
ButtonGroup.group: onOff
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
checked: true
text: "ON"
boxSize: 20
}
HifiConstants {
id: hifi
}
HifiControlsUit.RadioButton {
id: collisionsDisabledRadioButton
Layout.row: 1
Layout.column: 2
Layout.rightMargin: 20
ButtonGroup.group: onOff
colorScheme: hifi.colorSchemes.light
fontSize: 17
letterSpacing: 1.4
text: "OFF"
boxSize: 20
}
}
ColumnLayout {
id: avatarAnimationLayout
anchors.top: handAndCollisions.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.right: parent.right
spacing: 4
// TextStyle9
RalewaySemiBold {
size: 17;
text: "Avatar Animation JSON"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
}
InputTextStyle4 {
id: avatarAnimationUrlInputText
font.pixelSize: 17
anchors.left: parent.left
anchors.right: parent.right
placeholderText: 'user\\file\\dir'
}
}
ColumnLayout {
id: avatarCollisionLayout
anchors.top: avatarAnimationLayout.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.right: parent.right
spacing: 4
// TextStyle9
RalewaySemiBold {
size: 17;
text: "Avatar collision sound URL (optional)"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
}
InputTextStyle4 {
id: avatarCollisionSoundUrlInputText
font.pixelSize: 17
anchors.left: parent.left
anchors.right: parent.right
placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-'
}
}
DialogButtons {
id: dialogButtons
anchors.right: parent.right
anchors.bottom: parent.bottom
yesText: "SAVE"
noText: "CANCEL"
}
}
}

View file

@ -0,0 +1,29 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
property alias text: glyph.text
property alias font: glyph.font
property alias color: glyph.color
property alias horizontalAlignment: glyph.horizontalAlignment
property alias dropShadowRadius: shadow.radius
property alias dropShadowHorizontalOffset: shadow.horizontalOffset
property alias dropShadowVerticalOffset: shadow.verticalOffset
HiFiGlyphs {
id: glyph
width: parent.width
height: parent.height
}
DropShadow {
id: shadow
anchors.fill: glyph
radius: 4
horizontalOffset: 0
verticalOffset: 4
color: Qt.rgba(0, 0, 0, 0.25)
source: glyph
}
}

View file

@ -0,0 +1,32 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
property alias source: image.source
property alias dropShadowRadius: shadow.radius
property alias dropShadowHorizontalOffset: shadow.horizontalOffset
property alias dropShadowVerticalOffset: shadow.verticalOffset
property alias radius: image.radius
property alias border: image.border
property alias status: image.status
property alias progress: image.progress
property alias fillMode: image.fillMode
RoundImage {
id: image
width: parent.width
height: parent.height
radius: 6
}
DropShadow {
id: shadow
anchors.fill: image
radius: 6
horizontalOffset: 0
verticalOffset: 3
color: Qt.rgba(0, 0, 0, 0.25)
source: image
}
}

View file

@ -0,0 +1,30 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
Item {
property alias color: rectangle.color
property alias gradient: rectangle.gradient
property alias border: rectangle.border
property alias radius: rectangle.radius
property alias dropShadowRadius: shadow.radius
property alias dropShadowHorizontalOffset: shadow.horizontalOffset
property alias dropShadowVerticalOffset: shadow.verticalOffset
property alias dropShadowOpacity: shadow.opacity
Rectangle {
id: rectangle
width: parent.width
height: parent.height
}
DropShadow {
id: shadow
anchors.fill: rectangle
radius: 6
horizontalOffset: 0
verticalOffset: 3
color: Qt.rgba(0, 0, 0, 0.25)
source: rectangle
}
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.5
import QtWebEngine 1.5
AnimatedImage {
source: "../../../icons/loader-snake-64-w.gif"
playing: visible
}

View file

@ -0,0 +1,29 @@
import "../../styles-uit"
import QtQuick 2.9
import QtGraphicalEffects 1.0
ShadowRectangle {
width: 44
height: 28
AvatarAppStyle {
id: style
}
gradient: Gradient {
GradientStop { position: 0.0; color: style.colors.blueHighlight }
GradientStop { position: 1.0; color: style.colors.blueAccent }
}
property alias glyphText: glyph.text
property alias glyphRotation: glyph.rotation
property alias glyphSize: glyph.size
radius: 3
HiFiGlyphs {
id: glyph
color: 'white'
anchors.centerIn: parent
size: 30
}
}

View file

@ -0,0 +1,43 @@
import QtQuick 2.0
Item {
property alias source: sourceImage.sourceItem
property alias maskSource: sourceMask.sourceItem
anchors.fill: parent
ShaderEffectSource {
id: sourceMask
smooth: true
hideSource: true
}
ShaderEffectSource {
id: sourceImage
hideSource: true
}
ShaderEffect {
id: maskEffect
anchors.fill: parent
property variant source: sourceImage
property variant mask: sourceMask
fragmentShader: {
"
varying highp vec2 qt_TexCoord0;
uniform lowp sampler2D source;
uniform lowp sampler2D mask;
void main() {
highp vec4 maskColor = texture2D(mask, vec2(qt_TexCoord0.x, qt_TexCoord0.y));
highp vec4 sourceColor = texture2D(source, vec2(qt_TexCoord0.x, qt_TexCoord0.y));
if(maskColor.a > 0.0)
gl_FragColor = sourceColor;
else
gl_FragColor = maskColor;
}
"
}
}
}

View file

@ -0,0 +1,65 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
Row {
id: root
width: parent.width
height: xspinner.controlHeight
property int spinboxSpace: 10
property int spinboxWidth: (parent.width - 2 * spinboxSpace) / 3
property color backgroundColor: "darkgray"
property int decimals: 4
property real realFrom: 0
property real realTo: 100
property real realStepSize: 0.0001
spacing: spinboxSpace
property alias xvalue: xspinner.realValue
property alias yvalue: yspinner.realValue
property alias zvalue: zspinner.realValue
HifiControlsUit.SpinBox {
id: xspinner
width: parent.spinboxWidth
labelInside: "X:"
backgroundColor: parent.backgroundColor
colorLabelInside: hifi.colors.redHighlight
colorScheme: hifi.colorSchemes.light
decimals: root.decimals;
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
}
HifiControlsUit.SpinBox {
id: yspinner
width: parent.spinboxWidth
labelInside: "Y:"
backgroundColor: parent.backgroundColor
colorLabelInside: hifi.colors.greenHighlight
colorScheme: hifi.colorSchemes.light
decimals: root.decimals;
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
}
HifiControlsUit.SpinBox {
id: zspinner
width: parent.spinboxWidth
labelInside: "Z:"
backgroundColor: parent.backgroundColor
colorLabelInside: hifi.colors.primaryHighlight
colorScheme: hifi.colorSchemes.light
decimals: root.decimals;
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
}
}

View file

@ -0,0 +1,15 @@
import QtQuick 2.5
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
HifiControlsUit.Button {
HifiConstants {
id: hifi
}
width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20)
fontSize: 18
color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.light;
height: 40
}

View file

@ -51,6 +51,8 @@ ListModel {
if (!delayedClear) { root.clear(); }
currentPageToRetrieve = 1;
retrievedAtLeastOnePage = false;
totalPages = 0;
totalEntries = 0;
}
// Page processing.
@ -59,6 +61,8 @@ ListModel {
property var processPage: function (data) { return data; }
property var listView; // Optional. For debugging.
property int totalPages: 0;
property int totalEntries: 0;
// Check consistency and call processPage.
function handlePage(error, response) {
var processed;
@ -79,8 +83,10 @@ ListModel {
if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property.
return fail("Mismatched page, expected:" + currentPageToRetrieve);
}
totalPages = response.total_pages || 0;
totalEntries = response.total_entries || 0;
processed = processPage(response.data || response);
if (response.total_pages && (response.total_pages === currentPageToRetrieve)) {
if (totalPages && (totalPages === currentPageToRetrieve)) {
currentPageToRetrieve = -1;
}

View file

@ -300,6 +300,12 @@ Item {
id: controllerPrefereneces
objectName: "TabletControllerPreferences"
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
categoryProperties: {
"VR Movement" : {
"User real-world height (meters)" : { "anchors.right" : "undefined" },
"RESET SENSORS" : { "width" : "180", "anchors.left" : "undefined" }
}
}
}
}
}

View file

@ -0,0 +1,15 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtWebChannel 1.0
import "../../controls"
import "../toolbars"
import QtGraphicalEffects 1.0
import "../../controls-uit" as HifiControls
import "../../styles-uit"
WebView {
id: entityListToolWebView
url: Paths.defaultScripts + "/system/html/entityList.html"
enabled: true
}

View file

@ -9,7 +9,6 @@ import "../../styles-uit"
TabBar {
id: editTabView
// anchors.fill: parent
width: parent.width
contentWidth: parent.width
padding: 0
@ -34,7 +33,7 @@ TabBar {
width: parent.width
clip: true
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
assetServerButton.anchors.topMargin + importButton.anchors.topMargin +
header.paintedHeight
@ -77,8 +76,9 @@ TabBar {
text: "MODEL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newModelButton" }
});
editTabView.currentIndex = 2
}
}
@ -88,8 +88,9 @@ TabBar {
text: "CUBE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = 2
}
}
@ -99,8 +100,9 @@ TabBar {
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newSphereButton" }
});
editTabView.currentIndex = 2
}
}
@ -110,8 +112,9 @@ TabBar {
text: "LIGHT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newLightButton" }
});
editTabView.currentIndex = 2
}
}
@ -121,8 +124,9 @@ TabBar {
text: "TEXT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newTextButton" }
});
editTabView.currentIndex = 2
}
}
@ -132,8 +136,9 @@ TabBar {
text: "IMAGE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newImageButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newImageButton" }
});
editTabView.currentIndex = 2
}
}
@ -143,8 +148,9 @@ TabBar {
text: "WEB"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newWebButton" }
});
editTabView.currentIndex = 2
}
}
@ -154,8 +160,9 @@ TabBar {
text: "ZONE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newZoneButton" }
});
editTabView.currentIndex = 2
}
}
@ -165,8 +172,9 @@ TabBar {
text: "PARTICLE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newParticleButton" }
});
editTabView.currentIndex = 4
}
}
@ -176,8 +184,9 @@ TabBar {
text: "MATERIAL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "newMaterialButton" }
});
editTabView.currentIndex = 2
}
}
@ -196,8 +205,9 @@ TabBar {
anchors.topMargin: 35
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "openAssetBrowserButton" }
});
}
}
@ -214,8 +224,9 @@ TabBar {
anchors.topMargin: 20
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" }
});
method: "newEntityButtonClicked",
params: { buttonName: "importEntitiesButton" }
});
}
}
}

View file

@ -0,0 +1,58 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
// FIXME pretty non-DRY code, should figure out a way to optionally hide one tab from the tab view, keep in sync with Edit.qml
StackView {
id: editRoot
objectName: "stack"
signal sendToScript(var message);
topPadding: 40
leftPadding: 0
rightPadding: 0
bottomPadding: 0
anchors.fill: parent
property var itemProperties: {"y": editRoot.topPadding,
"width": editRoot.availableWidth,
"height": editRoot.availableHeight }
Component.onCompleted: {
tab.currentIndex = 0
}
background: Rectangle {
color: "#404040" //default background color
EditToolsTabView {
id: tab
anchors.fill: parent
currentIndex: -1
onCurrentIndexChanged: {
editRoot.replace(null, tab.itemAt(currentIndex).visualItem,
itemProperties,
StackView.Immediate)
}
}
}
function pushSource(path) {
editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties,
StackView.Immediate);
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
}
function popSource() {
editRoot.pop(StackView.Immediate);
}
// Passes script messages to the item on the top of the stack
function fromScript(message) {
var currentItem = editRoot.currentItem;
if (currentItem && currentItem.fromScript) {
currentItem.fromScript(message);
} else if (tab.fromScript) {
tab.fromScript(message);
}
}
}

View file

@ -0,0 +1,328 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtWebChannel 1.0
import "../../controls"
import "../toolbars"
import QtGraphicalEffects 1.0
import "../../controls-uit" as HifiControls
import "../../styles-uit"
TabBar {
id: editTabView
width: parent.width
contentWidth: parent.width
padding: 0
spacing: 0
readonly property QtObject tabIndex: QtObject {
readonly property int create: 0
readonly property int properties: 1
readonly property int grid: 2
readonly property int particle: 3
}
readonly property HifiConstants hifi: HifiConstants {}
EditTabButton {
title: "CREATE"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
Rectangle {
color: "#404040"
id: container
Flickable {
height: parent.height
width: parent.width
clip: true
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
assetServerButton.anchors.topMargin + importButton.anchors.topMargin +
header.paintedHeight
contentWidth: width
ScrollBar.vertical : ScrollBar {
visible: parent.contentHeight > parent.height
width: 20
background: Rectangle {
color: hifi.colors.tableScrollBackgroundDark
}
}
Text {
id: header
color: "#ffffff"
text: "Choose an Entity Type to Create:"
font.pixelSize: 14
font.bold: true
anchors.top: parent.top
anchors.topMargin: 28
anchors.left: parent.left
anchors.leftMargin: 28
}
Flow {
id: createEntitiesFlow
spacing: 35
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: parent.top
anchors.topMargin: 70
NewEntityButton {
icon: "icons/create-icons/94-model-01.svg"
text: "MODEL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newModelButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/21-cube-01.svg"
text: "CUBE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newCubeButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/22-sphere-01.svg"
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newSphereButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/24-light-01.svg"
text: "LIGHT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newLightButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/20-text-01.svg"
text: "TEXT"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newTextButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/image.svg"
text: "IMAGE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newImageButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/25-web-1-01.svg"
text: "WEB"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newWebButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/23-zone-01.svg"
text: "ZONE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newZoneButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
NewEntityButton {
icon: "icons/create-icons/90-particles-01.svg"
text: "PARTICLE"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newParticleButton" }
});
editTabView.currentIndex = tabIndex.particle
}
}
NewEntityButton {
icon: "icons/create-icons/126-material-01.svg"
text: "MATERIAL"
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "newMaterialButton" }
});
editTabView.currentIndex = tabIndex.properties
}
}
}
HifiControls.Button {
id: assetServerButton
text: "Open This Domain's Asset Server"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: createEntitiesFlow.bottom
anchors.topMargin: 35
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "openAssetBrowserButton" }
});
}
}
HifiControls.Button {
id: importButton
text: "Import Entities (.json)"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors.right: parent.right
anchors.rightMargin: 55
anchors.left: parent.left
anchors.leftMargin: 55
anchors.top: assetServerButton.bottom
anchors.topMargin: 20
onClicked: {
editRoot.sendToScript({
method: "newEntityButtonClicked",
params: { buttonName: "importEntitiesButton" }
});
}
}
}
} // Flickable
}
}
EditTabButton {
title: "PROPERTIES"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
WebView {
id: entityPropertiesWebView
url: Paths.defaultScripts + "/system/html/entityProperties.html"
enabled: true
}
}
}
EditTabButton {
title: "GRID"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
WebView {
id: gridControlsWebView
url: Paths.defaultScripts + "/system/html/gridControls.html"
enabled: true
}
}
}
EditTabButton {
title: "P"
active: true
enabled: true
property string originalUrl: ""
property Component visualItem: Component {
WebView {
id: particleExplorerWebView
url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html"
enabled: true
}
}
}
function fromScript(message) {
switch (message.method) {
case 'selectTab':
selectTab(message.params.id);
break;
default:
console.warn('Unrecognized message:', JSON.stringify(message));
}
}
// Changes the current tab based on tab index or title as input
function selectTab(id) {
if (typeof id === 'number') {
if (id >= tabIndex.create && id <= tabIndex.particle) {
editTabView.currentIndex = id;
} else {
console.warn('Attempt to switch to invalid tab:', id);
}
} else if (typeof id === 'string'){
switch (id.toLowerCase()) {
case 'create':
editTabView.currentIndex = tabIndex.create;
break;
case 'properties':
editTabView.currentIndex = tabIndex.properties;
break;
case 'grid':
editTabView.currentIndex = tabIndex.grid;
break;
case 'particle':
editTabView.currentIndex = tabIndex.particle;
break;
default:
console.warn('Attempt to switch to invalid tab:', id);
}
} else {
console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id));
}
}
}

View file

@ -0,0 +1,5 @@
WebView {
id: entityListToolWebView
url: Paths.defaultScripts + "/system/html/entityList.html"
enabled: true
}

View file

@ -29,12 +29,16 @@ Rectangle {
property bool keyboardRasied: false
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
try {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
} catch(e) {
Window.alert(message);
}
}
Item {

View file

@ -0,0 +1,20 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
StackView {
id: stackView
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 40
signal sendToScript(var message);
NewMaterialDialog {
id: dialog
anchors.fill: parent
Component.onCompleted:{
dialog.sendToScript.connect(stackView.sendToScript);
}
}
}

View file

@ -29,12 +29,16 @@ Rectangle {
property bool keyboardRasied: false
function errorMessageBox(message) {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
try {
return desktop.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
} catch(e) {
Window.alert(message);
}
}
Item {

View file

@ -0,0 +1,20 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
StackView {
id: stackView
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 40
signal sendToScript(var message);
NewModelDialog {
id: dialog
anchors.fill: parent
Component.onCompleted:{
dialog.sendToScript.connect(stackView.sendToScript);
}
}
}

View file

@ -89,6 +89,7 @@ StackView {
property bool keyboardEnabled: false
property bool punctuationMode: false
property bool keyboardRaised: false
width: parent.width
height: parent.height
@ -210,6 +211,8 @@ StackView {
QQC2.TextField {
id: addressLine
focus: true
width: addressLineContainer.width - addressLineContainer.anchors.leftMargin - addressLineContainer.anchors.rightMargin;
anchors {
left: addressLineContainer.left;
@ -236,24 +239,20 @@ StackView {
color: hifi.colors.text
background: Item {}
QQC2.Label {
T.TextField {
id: control
}
padding: 6 // numbers taken from Qt\5.9.2\Src\qtquickcontrols2\src\imports\controls\TextField.qml
leftPadding: padding + 4
}
QQC2.Label {
font: addressLine.font
font: parent.font
x: addressLine.x
y: addressLine.y
leftPadding: addressLine.leftPadding
topPadding: addressLine.topPadding
x: control.leftPadding
y: control.topPadding
text: parent.placeholderText2
verticalAlignment: "AlignVCenter"
color: 'gray'
visible: parent.text === ''
}
text: addressLine.placeholderText2
verticalAlignment: "AlignVCenter"
color: 'gray'
visible: addressLine.text === ''
}
Rectangle {

View file

@ -24,6 +24,7 @@ Item {
HifiConstants { id: hifi }
property var sections: []
property var showCategories: []
property var categoryProperties: ({})
property bool keyboardEnabled: false
property bool keyboardRaised: false
@ -100,7 +101,8 @@ Item {
// NOTE: the sort order of items in the showCategories array is the same order in the dialog.
for (i = 0; i < showCategories.length; i++) {
if (categoryMap[showCategories[i]]) {
sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i]}));
var properties = categoryProperties.hasOwnProperty(showCategories[i]) ? categoryProperties[showCategories[i]] : {};
sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i], sectionProperties: properties}));
}
}

View file

@ -24,6 +24,7 @@ Preference {
property bool isLast: false
property string name: "Header"
property real spacing: 8
property var sectionProperties: ({})
default property alias preferences: contentContainer.children
HifiConstants { id: hifi }
@ -163,6 +164,28 @@ Preference {
if (builder) {
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) , z: zpos}));
var preferenceObject = preferences[preferences.length - 1];
var props = sectionProperties.hasOwnProperty(preference.name) ? sectionProperties[preference.name] : {};
for(var prop in props) {
var value = props[prop];
if(value.indexOf('.') !== -1) {
var splittedValues = value.split('.');
if(splittedValues[0] === 'parent') {
value = preferenceObject.parent[splittedValues[1]];
}
} else if(value === 'undefined') {
value = undefined;
}
if(prop.indexOf('.') !== -1) {
var splittedProps = prop.split('.');
preferenceObject[splittedProps[0]][splittedProps[1]] = value;
} else {
preferenceObject[prop] = value;
}
}
}
}
}

View file

@ -10,6 +10,12 @@
//
#include "AndroidHelper.h"
#include <QDebug>
#include "Application.h"
#if defined(qApp)
#undef qApp
#endif
#define qApp (static_cast<Application*>(QCoreApplication::instance()))
AndroidHelper::AndroidHelper() {
}
@ -17,8 +23,8 @@ AndroidHelper::AndroidHelper() {
AndroidHelper::~AndroidHelper() {
}
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene) {
emit androidActivityRequested(activityName, backToScene);
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene, QList<QString> args) {
emit androidActivityRequested(activityName, backToScene, args);
}
void AndroidHelper::notifyLoadComplete() {
@ -40,3 +46,9 @@ void AndroidHelper::performHapticFeedback(int duration) {
void AndroidHelper::showLoginDialog() {
emit androidActivityRequested("Login", true);
}
void AndroidHelper::processURL(const QString &url) {
if (qApp->canAcceptURL(url)) {
qApp->acceptURL(url);
}
}

View file

@ -21,12 +21,13 @@ public:
static AndroidHelper instance;
return instance;
}
void requestActivity(const QString &activityName, const bool backToScene);
void requestActivity(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
void notifyLoadComplete();
void notifyEnterForeground();
void notifyEnterBackground();
void performHapticFeedback(int duration);
void processURL(const QString &url);
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
@ -35,7 +36,7 @@ public slots:
void showLoginDialog();
signals:
void androidActivityRequested(const QString &activityName, const bool backToScene);
void androidActivityRequested(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
void qtAppLoadComplete();
void enterForeground();
void enterBackground();

View file

@ -30,6 +30,7 @@
#include <QtCore/QFileSelector>
#include <QtConcurrent/QtConcurrentRun>
#include <QtGui/QClipboard>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtGui/QDesktopServices>
@ -129,6 +130,7 @@
#include <SoundCache.h>
#include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h>
#include <InteractiveWindow.h>
#include <Tooltip.h>
#include <udt/PacketHeaders.h>
#include <UserActivityLogger.h>
@ -1011,6 +1013,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf");
_window->setWindowTitle("High Fidelity Interface");
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
@ -2059,6 +2062,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|| ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose));
lastLeftHandPose = leftHandPose;
lastRightHandPose = rightHandPose;
properties["avatar_identity_requests_sent"] = DependencyManager::get<AvatarManager>()->getIdentityRequestsSent();
UserActivityLogger::getInstance().logAction("stats", properties);
});
@ -2923,6 +2927,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("Overlays", &_overlays);
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
surfaceContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
@ -3963,7 +3968,18 @@ void Application::mousePressEvent(QMouseEvent* event) {
return;
}
#if defined(Q_OS_MAC)
// Fix for OSX right click dragging on window when coming from a native window
bool isFocussed = hasFocus();
if (!isFocussed && event->button() == Qt::MouseButton::RightButton) {
setFocus();
isFocussed = true;
}
if (isFocussed) {
#else
if (hasFocus()) {
#endif
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->mousePressEvent(event);
}
@ -6588,6 +6604,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
registerInteractiveWindowMetaType(scriptEngine.data());
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
@ -7574,7 +7592,6 @@ void Application::toggleEntityScriptServerLogDialog() {
void Application::loadAddAvatarBookmarkDialog() const {
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
avatarBookmarks->addBookmark();
}
void Application::loadAvatarBrowser() const {
@ -8256,6 +8273,16 @@ void Application::saveNextPhysicsStats(QString filename) {
_physicsEngine->saveNextPhysicsStats(filename);
}
void Application::copyToClipboard(const QString& text) {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "copyToClipboard");
return;
}
// assume that the address is being copied because the user wants a shareable address
QApplication::clipboard()->setText(text);
}
#if defined(Q_OS_ANDROID)
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),

View file

@ -317,6 +317,8 @@ public:
void loadAvatarScripts(const QVector<QString>& urls);
void unloadAvatarScripts();
Q_INVOKABLE void copyToClipboard(const QString& text);
#if defined(Q_OS_ANDROID)
void enterBackground();
void enterForeground();

View file

@ -17,7 +17,9 @@
#include <QStandardPaths>
#include <QQmlContext>
#include <QList>
#include <QtCore/QThread>
#include <shared/QtHelpers.h>
#include <Application.h>
#include <OffscreenUi.h>
#include <avatar/AvatarManager.h>
@ -28,7 +30,6 @@
#include <VariantMapToScriptValue.h>
#include "MainWindow.h"
#include "Menu.h"
#include "InterfaceLogging.h"
#include "QVariantGLM.h"
@ -92,10 +93,96 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
}
AvatarBookmarks::AvatarBookmarks() {
QDir directory(PathUtils::getAppDataPath());
if (!directory.exists()) {
directory.mkpath(".");
}
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
if(!QFile::exists(_bookmarksFilename)) {
auto defaultBookmarksFilename = PathUtils::resourcesPath() + QString("avatar/bookmarks") + "/" + AVATARBOOKMARKS_FILENAME;
if (!QFile::exists(defaultBookmarksFilename)) {
qDebug() << defaultBookmarksFilename << "doesn't exist!";
}
if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) {
qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename;
}
}
readFromFile();
}
void AvatarBookmarks::addBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_ARG(QString, bookmarkName));
return;
}
QVariantMap bookmark = getAvatarDataToBookmark();
insert(bookmarkName, bookmark);
emit bookmarkAdded(bookmarkName);
}
void AvatarBookmarks::saveBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_ARG(QString, bookmarkName));
return;
}
if (contains(bookmarkName)) {
QVariantMap bookmark = getAvatarDataToBookmark();
insert(bookmarkName, bookmark);
}
}
void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_ARG(QString, bookmarkName));
return;
}
remove(bookmarkName);
emit bookmarkDeleted(bookmarkName);
}
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
myAvatar->removeAvatarEntities();
addAvatarEntities(avatarEntities);
}
void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName));
return;
}
auto bookmarkEntry = _bookmarks.find(bookmarkName);
if (bookmarkEntry != _bookmarks.end()) {
QVariantMap bookmark = bookmarkEntry.value().toMap();
if (!bookmark.empty()) {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
myAvatar->removeAvatarEntities();
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
qCDebug(interfaceapp) << "Attach " << attachments;
myAvatar->setAttachmentsVariant(attachments);
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(qScale);
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
addAvatarEntities(avatarEntities);
emit bookmarkLoaded(bookmarkName);
}
}
}
void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
@ -115,99 +202,37 @@ void AvatarBookmarks::readFromFile() {
Bookmarks::readFromFile();
}
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
// Add menus/actions
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
_bookmarksMenu = menu->addMenu(MenuOption::AvatarBookmarks);
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarBookmark);
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
addBookmarkToMenu(menubar, it.key(), it.value());
QVariantMap AvatarBookmarks::getBookmark(const QString &bookmarkName)
{
if (QThread::currentThread() != thread()) {
QVariantMap result;
BLOCKING_INVOKE_METHOD(this, "getBookmark", Q_RETURN_ARG(QVariantMap, result), Q_ARG(QString, bookmarkName));
return result;
}
Bookmarks::sortActions(menubar, _bookmarksMenu);
QVariantMap bookmark;
auto bookmarkEntry = _bookmarks.find(bookmarkName);
if (bookmarkEntry != _bookmarks.end()) {
bookmark = bookmarkEntry.value().toMap();
}
return bookmark;
}
void AvatarBookmarks::changeToBookmarkedAvatar() {
QAction* action = qobject_cast<QAction*>(sender());
QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
if (action->data().type() == QVariant::String) {
// TODO: Phase this out eventually.
// Legacy avatar bookmark.
myAvatar->useFullAvatarURL(action->data().toString());
qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark ";
} else {
const QMap<QString, QVariant> bookmark = action->data().toMap();
// Not magic value. This is the current made version, and if it changes this interpreter should be updated to
// handle the new one separately.
// This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous
if (bookmark.value(ENTRY_VERSION) == 3) {
myAvatar->removeAvatarEntities();
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
qCDebug(interfaceapp) << "Attach " << attachments;
myAvatar->setAttachmentsVariant(attachments);
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(qScale);
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
addAvatarEntities(avatarEntities);
} else {
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark";
}
}
}
void AvatarBookmarks::addBookmark() {
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString());
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
auto bookmarkName = response.toString();
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
QVariantMap bookmark;
bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
});
}
void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
QAction* changeAction = _bookmarksMenu->newAction();
changeAction->setData(bookmark);
connect(changeAction, SIGNAL(triggered()), this, SLOT(changeToBookmarkedAvatar()));
if (!_isMenuSorted) {
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
} else {
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
Bookmarks::sortActions(menubar, _bookmarksMenu);
}
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
QVariantMap bookmark;
bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
return bookmark;
}

View file

@ -30,19 +30,50 @@ class AvatarBookmarks: public Bookmarks, public Dependency {
public:
AvatarBookmarks();
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
void setupMenus(Menu* menubar, MenuWrapper* menu) override {};
Q_INVOKABLE QVariantMap getBookmark(const QString& bookmarkName);
public slots:
/**jsdoc
* Add the current Avatar to your avatar bookmarks.
* @function AvatarBookmarks.addBookMark
*/
void addBookmark();
void addBookmark(const QString& bookmarkName);
void saveBookmark(const QString& bookmarkName);
void loadBookmark(const QString& bookmarkName);
void removeBookmark(const QString& bookmarkName);
void updateAvatarEntities(const QVariantList& avatarEntities);
QVariantMap getBookmarks() { return _bookmarks; }
signals:
/**jsdoc
* This function gets triggered after avatar loaded from bookmark
* @function AvatarBookmarks.bookmarkLoaded
* @param {string} bookmarkName
* @returns {Signal}
*/
void bookmarkLoaded(const QString& bookmarkName);
/**jsdoc
* This function gets triggered after avatar bookmark deleted
* @function AvatarBookmarks.bookmarkDeleted
* @param {string} bookmarkName
* @returns {Signal}
*/
void bookmarkDeleted(const QString& bookmarkName);
/**jsdoc
* This function gets triggered after avatar bookmark added
* @function AvatarBookmarks.bookmarkAdded
* @param {string} bookmarkName
* @returns {Signal}
*/
void bookmarkAdded(const QString& bookmarkName);
protected:
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override {};
void readFromFile() override;
QVariantMap getAvatarDataToBookmark();
private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
@ -53,9 +84,6 @@ private:
const QString ENTRY_VERSION = "version";
const int AVATAR_BOOKMARK_VERSION = 3;
private slots:
void changeToBookmarkedAvatar();
};
#endif // hifi_AvatarBookmarks_h

View file

@ -46,6 +46,10 @@ void Bookmarks::deleteBookmark() {
return;
}
deleteBookmark(bookmarkName);
}
void Bookmarks::deleteBookmark(const QString& bookmarkName) {
removeBookmarkFromMenu(Menu::getInstance(), bookmarkName);
remove(bookmarkName);

View file

@ -31,6 +31,8 @@ public:
QString addressForBookmark(const QString& name) const;
protected:
void deleteBookmark(const QString& bookmarkName);
void addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark);
virtual void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) = 0;
void enableMenuItems(bool enabled);
@ -38,9 +40,10 @@ protected:
void insert(const QString& name, const QVariant& address); // Overwrites any existing entry with same name.
void sortActions(Menu* menubar, MenuWrapper* menu);
int getMenuItemLocation(QList<QAction*> actions, const QString& name) const;
void removeBookmarkFromMenu(Menu* menubar, const QString& name);
bool contains(const QString& name) const;
void remove(const QString& name);
QVariantMap _bookmarks; // { name: url, ... }
QPointer<MenuWrapper> _bookmarksMenu;
QPointer<QAction> _deleteBookmarksAction;
@ -57,12 +60,9 @@ protected slots:
void deleteBookmark();
private:
void remove(const QString& name);
static bool sortOrder(QAction* a, QAction* b);
void persistToFile();
void removeBookmarkFromMenu(Menu* menubar, const QString& name);
};
#endif // hifi_Bookmarks_h

View file

@ -158,46 +158,6 @@ Menu::Menu() {
// Edit > Reload All Content
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
MenuWrapper* avatarMenu = addMenu("Avatar");
auto avatarManager = DependencyManager::get<AvatarManager>();
auto avatar = avatarManager->getMyAvatar();
// Avatar > Size
MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size");
// Avatar > Size > Increase
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::IncreaseAvatarSize,
0, // QML Qt::Key_Plus,
avatar.get(), SLOT(increaseSize()));
// Avatar > Size > Decrease
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::DecreaseAvatarSize,
0, // QML Qt::Key_Minus,
avatar.get(), SLOT(decreaseSize()));
// Avatar > Size > Reset
addActionToQMenuAndActionHash(avatarSizeMenu,
MenuOption::ResetAvatarSize,
0, // QML Qt::Key_Equal,
avatar.get(), SLOT(resetSize()));
// Avatar > Reset Sensors
addActionToQMenuAndActionHash(avatarMenu,
MenuOption::ResetSensors,
0, // QML Qt::Key_Apostrophe,
qApp, SLOT(resetSensors()));
// Avatar > Attachments...
action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments);
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"),
QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog");
});
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
avatarBookmarks->setupMenus(this, avatarMenu);
// Display menu ----------------------------------
// FIXME - this is not yet matching Alan's spec because it doesn't have
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
@ -315,13 +275,6 @@ Menu::Menu() {
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
});
// Settings > Avatar...
action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
QString("hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog");
});
// Settings > Developer Menu
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));
@ -580,6 +533,9 @@ Menu::Menu() {
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
auto avatarManager = DependencyManager::get<AvatarManager>();
auto avatar = avatarManager->getMyAvatar();
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true);
connect(action, &QAction::triggered, [this, avatar]{
avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping));

View file

@ -15,6 +15,8 @@
#include <QScriptEngine>
#include "AvatarLogging.h"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdouble-promotion"
@ -54,6 +56,13 @@ static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND /
// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
const QUuid MY_AVATAR_KEY; // NULL key
namespace {
// For an unknown avatar-data packet, wait this long before requesting the identity.
constexpr std::chrono::milliseconds REQUEST_UNKNOWN_IDENTITY_DELAY { 5 * 1000 };
constexpr int REQUEST_UNKNOWN_IDENTITY_TRANSMITS = 3;
}
using std::chrono::steady_clock;
AvatarManager::AvatarManager(QObject* parent) :
_avatarsToFade(),
_myAvatar(new MyAvatar(qApp->thread()), [](MyAvatar* ptr) { ptr->deleteLater(); })
@ -118,6 +127,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
_lastSendAvatarDataTime = now;
_myAvatarSendRate.increment();
}
}
@ -286,6 +296,28 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
simulateAvatarFades(deltaTime);
// Check on avatars with pending identities:
steady_clock::time_point now = steady_clock::now();
QWriteLocker writeLock(&_hashLock);
for (auto pendingAvatar = _pendingAvatars.begin(); pendingAvatar != _pendingAvatars.end(); ++pendingAvatar) {
if (now - pendingAvatar->creationTime >= REQUEST_UNKNOWN_IDENTITY_DELAY) {
// Too long without an ID
sendIdentityRequest(pendingAvatar->avatar->getID());
if (++pendingAvatar->transmits >= REQUEST_UNKNOWN_IDENTITY_TRANSMITS) {
qCDebug(avatars) << "Requesting identity for unknown avatar (final request)" <<
pendingAvatar->avatar->getID().toString();
pendingAvatar = _pendingAvatars.erase(pendingAvatar);
if (pendingAvatar == _pendingAvatars.end()) {
break;
}
} else {
pendingAvatar->creationTime = now;
qCDebug(avatars) << "Requesting identity for unknown avatar" << pendingAvatar->avatar->getID().toString();
}
}
}
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
}
@ -298,6 +330,20 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen
}
}
void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->eachMatchingNode(
[&](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::AvatarMixer && node->getActiveSocket();
},
[&](const SharedNodePointer& node) {
auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true);
packet->write(avatarID.toRfc4122());
nodeList->sendPacket(std::move(packet), *node);
++_identityRequestsSent;
});
}
void AvatarManager::simulateAvatarFades(float deltaTime) {
if (_avatarsToFade.empty()) {
return;

View file

@ -82,6 +82,7 @@ public:
void updateMyAvatar(float deltaTime);
void updateOtherAvatars(float deltaTime);
void sendIdentityRequest(const QUuid& avatarID) const;
void postUpdate(float deltaTime, const render::ScenePointer& scene);
@ -157,6 +158,7 @@ public:
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
int getIdentityRequestsSent() const { return _identityRequestsSent; }
public slots:
@ -194,6 +196,7 @@ private:
int _numAvatarsNotUpdated { 0 };
float _avatarSimulationTime { 0.0f };
bool _shouldRender { true };
mutable int _identityRequestsSent { 0 };
};
#endif // hifi_AvatarManager_h

View file

@ -88,6 +88,10 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
const float MIN_SCALE_CHANGED_DELTA = 0.001f;
const int MODE_READINGS_RING_BUFFER_SIZE = 500;
const float CENTIMETERS_PER_METER = 100.0f;
//#define DEBUG_DRAW_HMD_MOVING_AVERAGE
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
@ -108,6 +112,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_hmdSensorMatrix(),
_hmdSensorOrientation(),
_hmdSensorPosition(),
_recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE),
_bodySensorMatrix(),
_goToPending(false),
_goToPosition(),
@ -414,7 +419,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
void MyAvatar::update(float deltaTime) {
// update moving average of HMD facing in xz plane.
const float HMD_FACING_TIMESCALE = 4.0f; // very slow average
const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
float tau = deltaTime / HMD_FACING_TIMESCALE;
_headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
@ -423,6 +429,12 @@ void MyAvatar::update(float deltaTime) {
_smoothOrientationTimer += deltaTime;
}
float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y;
int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER);
_recentModeReadings.insert(newHeightReadingInCentimeters);
setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
@ -715,7 +727,7 @@ void MyAvatar::simulate(float deltaTime) {
}
});
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
_characterController.setFlyingAllowed(zoneAllowsFlying && (_enableFlying || !isPhysicsEnabled));
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
_characterController.setCollisionlessAllowed(collisionlessAllowed);
}
@ -1591,18 +1603,26 @@ void MyAvatar::removeAvatarEntities() {
QVariantList MyAvatar::getAvatarEntitiesVariant() {
QVariantList avatarEntitiesData;
QScriptEngine scriptEngine;
forEachChild([&](SpatiallyNestablePointer child) {
if (child->getNestableType() == NestableType::Entity) {
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(child);
if (modelEntity) {
QVariantMap avatarEntityData;
EntityItemProperties entityProperties = modelEntity->getProperties();
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
avatarEntitiesData.append(QVariant(avatarEntityData));
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity) {
continue;
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
avatarEntitiesData.append(QVariant(avatarEntityData));
}
});
}
return avatarEntitiesData;
}
@ -1979,7 +1999,6 @@ QUrl MyAvatar::getAnimGraphUrl() const {
}
void MyAvatar::setAnimGraphUrl(const QUrl& url) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAnimGraphUrl", Q_ARG(QUrl, url));
return;
@ -1988,6 +2007,9 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) {
if (_currentAnimGraphUrl.get() == url) {
return;
}
emit animGraphUrlChanged(url);
destroyAnimGraph();
_skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render.
@ -2172,6 +2194,15 @@ void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement)
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
}
void MyAvatar::setRotationRecenterFilterLength(float length) {
const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f;
_rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length);
}
void MyAvatar::setRotationThreshold(float angleRadians) {
_rotationThreshold = angleRadians;
}
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
@ -2812,6 +2843,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) {
}
_characterController.setCollisionless(!enabled);
emit collisionsEnabledChanged(enabled);
}
bool MyAvatar::getCollisionsEnabled() {
@ -3135,6 +3167,148 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
return worldToSensorMat * avatarToWorldMat * avatarHipsMat;
}
static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) {
return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0);
}
static bool withinBaseOfSupport(const controller::Pose& head) {
float userScale = 1.0f;
glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD);
glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD);
glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD);
glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD);
bool isWithinSupport = false;
if (head.isValid()) {
bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation());
bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation());
bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) &&
isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation()));
isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase);
}
return isWithinSupport;
}
static bool headAngularVelocityBelowThreshold(const controller::Pose& head) {
glm::vec3 xzPlaneAngularVelocity(0.0f, 0.0f, 0.0f);
if (head.isValid()) {
xzPlaneAngularVelocity.x = head.getAngularVelocity().x;
xzPlaneAngularVelocity.z = head.getAngularVelocity().z;
}
float magnitudeAngularVelocity = glm::length(xzPlaneAngularVelocity);
bool isBelowThreshold = (magnitudeAngularVelocity < DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD);
return isBelowThreshold;
}
static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) {
bool isWithinThreshold = true;
if (head.isValid()) {
isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD;
}
return isWithinThreshold;
}
float MyAvatar::computeStandingHeightMode(const controller::Pose& head) {
const float MODE_CORRECTION_FACTOR = 0.02f;
int greatestFrequency = 0;
int mode = 0;
// init mode in meters to the current mode
float modeInMeters = getCurrentStandingHeight();
if (head.isValid()) {
std::map<int, int> freq;
for(auto recentModeReadingsIterator = _recentModeReadings.begin(); recentModeReadingsIterator != _recentModeReadings.end(); ++recentModeReadingsIterator) {
freq[*recentModeReadingsIterator] += 1;
if (freq[*recentModeReadingsIterator] > greatestFrequency) {
greatestFrequency = freq[*recentModeReadingsIterator];
mode = *recentModeReadingsIterator;
}
}
modeInMeters = ((float)mode) / CENTIMETERS_PER_METER;
if (!(modeInMeters > getCurrentStandingHeight())) {
// if not greater check for a reset
if (getResetMode() && qApp->isHMDMode()) {
setResetMode(false);
float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER);
modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER);
_recentModeReadings.clear();
} else {
// if not greater and no reset, keep the mode as it is
modeInMeters = getCurrentStandingHeight();
}
}
}
return modeInMeters;
}
static bool handDirectionMatchesHeadDirection(const controller::Pose& leftHand, const controller::Pose& rightHand, const controller::Pose& head) {
const float VELOCITY_EPSILON = 0.02f;
bool leftHandDirectionMatchesHead = true;
bool rightHandDirectionMatchesHead = true;
glm::vec3 xzHeadVelocity(head.velocity.x, 0.0f, head.velocity.z);
if (leftHand.isValid() && head.isValid()) {
glm::vec3 xzLeftHandVelocity(leftHand.velocity.x, 0.0f, leftHand.velocity.z);
if ((glm::length(xzLeftHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) {
float handDotHeadLeft = glm::dot(glm::normalize(xzLeftHandVelocity), glm::normalize(xzHeadVelocity));
leftHandDirectionMatchesHead = ((handDotHeadLeft > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD));
} else {
leftHandDirectionMatchesHead = false;
}
}
if (rightHand.isValid() && head.isValid()) {
glm::vec3 xzRightHandVelocity(rightHand.velocity.x, 0.0f, rightHand.velocity.z);
if ((glm::length(xzRightHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) {
float handDotHeadRight = glm::dot(glm::normalize(xzRightHandVelocity), glm::normalize(xzHeadVelocity));
rightHandDirectionMatchesHead = (handDotHeadRight > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD);
} else {
rightHandDirectionMatchesHead = false;
}
}
return leftHandDirectionMatchesHead && rightHandDirectionMatchesHead;
}
static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, const controller::Pose& rightHand) {
float leftHandXZAngularVelocity = 0.0f;
float rightHandXZAngularVelocity = 0.0f;
if (leftHand.isValid()) {
glm::vec3 xzLeftHandAngularVelocity(leftHand.angularVelocity.x, 0.0f, leftHand.angularVelocity.z);
leftHandXZAngularVelocity = glm::length(xzLeftHandAngularVelocity);
}
if (rightHand.isValid()) {
glm::vec3 xzRightHandAngularVelocity(rightHand.angularVelocity.x, 0.0f, rightHand.angularVelocity.z);
rightHandXZAngularVelocity = glm::length(xzRightHandAngularVelocity);
}
return ((leftHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD) &&
(rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD));
}
static bool headVelocityGreaterThanThreshold(const controller::Pose& head) {
float headVelocityMagnitude = 0.0f;
if (head.isValid()) {
headVelocityMagnitude = glm::length(head.getVelocity());
}
return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD;
}
glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) {
const float AVERAGING_RATE = 0.03f;
return safeLerp(_averageHeadRotation, head.getRotation(), AVERAGING_RATE);
}
static bool isHeadLevel(const controller::Pose& head, const glm::quat& averageHeadRotation) {
glm::vec3 diffFromAverageEulers(0.0f, 0.0f, 0.0f);
if (head.isValid()) {
glm::vec3 averageHeadEulers = glm::degrees(safeEulerAngles(averageHeadRotation));
glm::vec3 currentHeadEulers = glm::degrees(safeEulerAngles(head.getRotation()));
diffFromAverageEulers = averageHeadEulers - currentHeadEulers;
}
return ((fabs(diffFromAverageEulers.x) < DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE) && (fabs(diffFromAverageEulers.z) < DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE));
}
float MyAvatar::getUserHeight() const {
return _userHeight.get();
}
@ -3301,7 +3475,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
}
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold());
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
}
@ -3328,7 +3502,37 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
}
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
}
bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const {
// get the current readings
controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND);
bool stepDetected = false;
if (!withinBaseOfSupport(currentHeadPose) &&
headAngularVelocityBelowThreshold(currentHeadPose) &&
isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) &&
handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) &&
handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) &&
headVelocityGreaterThanThreshold(currentHeadPose) &&
isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) {
// a step is detected
stepDetected = true;
} else {
glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips"));
glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head"));
glm::vec3 currentHeadPosition = currentHeadPose.getTranslation();
float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition);
if (!isActive(Horizontal) &&
(glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) {
myAvatar.setResetMode(true);
stepDetected = true;
}
}
return stepDetected;
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -3348,9 +3552,16 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Rotation);
}
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Horizontal);
if (myAvatar.getCenterOfGravityModelEnabled()) {
if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) {
activate(Horizontal);
}
} else {
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Horizontal);
}
}
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Vertical);
}

View file

@ -31,6 +31,7 @@
#include "AtRestDetector.h"
#include "MyCharacterController.h"
#include "RingBufferHistory.h"
#include <ThreadSafeValueCache.h>
class AvatarActionHold;
@ -195,6 +196,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement)
Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement)
Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement)
Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength)
Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
@ -880,6 +883,12 @@ public:
virtual void rebuildCollisionShape() override;
const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
float getCurrentStandingHeight() const { return _currentStandingHeight; }
void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; }
const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; }
void setAverageHeadRotation(glm::quat rotation) { _averageHeadRotation = rotation; }
bool getResetMode() const { return _resetMode; }
void setResetMode(bool hasBeenReset) { _resetMode = hasBeenReset; }
void setControllerPoseInSensorFrame(controller::Action action, const controller::Pose& pose);
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
@ -888,7 +897,12 @@ public:
bool hasDriveInput() const;
QVariantList getAvatarEntitiesVariant();
/**jsdoc
* Function returns list of avatar entities
* @function MyAvatar.getAvatarEntitiesVariant()
* @returns {object[]}
*/
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
void removeAvatarEntities();
/**jsdoc
@ -1015,6 +1029,9 @@ public:
bool isReadyForPhysics() const;
float computeStandingHeightMode(const controller::Pose& head);
glm::quat computeAverageHeadRotation(const controller::Pose& head);
public slots:
/**jsdoc
@ -1294,6 +1311,22 @@ signals:
*/
void collisionWithEntity(const Collision& collision);
/**jsdoc
* Triggered when collisions with avatar enabled or disabled
* @function MyAvatar.collisionsEnabledChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void collisionsEnabledChanged(bool enabled);
/**jsdoc
* Triggered when avatar's animation url changes
* @function MyAvatar.animGraphUrlChanged
* @param {url} url
* @returns {Signal}
*/
void animGraphUrlChanged(const QUrl& url);
/**jsdoc
* @function MyAvatar.energyChanged
* @param {number} energy
@ -1387,6 +1420,10 @@ private:
bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); }
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
void setRotationRecenterFilterLength(float length);
float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; }
void setRotationThreshold(float angleRadians);
float getRotationThreshold() const { return _rotationThreshold; }
bool isMyAvatar() const override { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
virtual glm::vec3 getSkeletonPosition() const override;
@ -1495,6 +1532,8 @@ private:
float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT };
float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT };
std::atomic<bool> _hasScriptedBlendShapes { false };
std::atomic<float> _rotationRecenterFilterLength { 4.0f };
std::atomic<float> _rotationThreshold { 0.5235f }; // 30 degrees in radians
// working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix { glm::mat4() };
@ -1506,6 +1545,11 @@ private:
// cache head controller pose in sensor space
glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space)
glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space)
glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f };
float _currentStandingHeight { 0.0f };
bool _resetMode { true };
RingBufferHistory<int> _recentModeReadings;
// cache of the current body position and orientation of the avatar's body,
// in sensor space.
@ -1533,6 +1577,7 @@ private:
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const;
@ -1621,7 +1666,7 @@ private:
// load avatar scripts once when rig is ready
bool _shouldLoadScripts { false };
bool _haveReceivedHeightLimitsFromDomain = { false };
bool _haveReceivedHeightLimitsFromDomain { false };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -14,6 +14,8 @@
#include <QWindow>
#include <QScreen>
#include <shared/QtHelpers.h>
#include "Application.h"
#include "MainWindow.h"
#include <display-plugins/CompositorHelper.h>
@ -29,6 +31,14 @@ int DesktopScriptingInterface::getHeight() {
return size.height();
}
QVariantMap DesktopScriptingInterface::getPresentationMode() {
static QVariantMap presentationModes {
{ "VIRTUAL", Virtual },
{ "NATIVE", Native }
};
return presentationModes;
}
void DesktopScriptingInterface::setHUDAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}
@ -41,3 +51,14 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title)
DependencyManager::get<OffscreenUi>()->show(path, title);
}
InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) {
if (QThread::currentThread() != thread()) {
InteractiveWindowPointer interactiveWindow = nullptr;
BLOCKING_INVOKE_METHOD(this, "createWindow",
Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow),
Q_ARG(QString, sourceUrl),
Q_ARG(QVariantMap, properties));
return interactiveWindow;
}
return new InteractiveWindow(sourceUrl, properties);;
}

View file

@ -13,20 +13,48 @@
#define hifi_DesktopScriptingInterface_h
#include <QObject>
#include <QtScript/QScriptValue>
#include <DependencyManager.h>
#include "InteractiveWindow.h"
/**jsdoc
* @namespace Desktop
*
* @hifi-interface
* @hifi-client-entity
*
* @property {number} width
* @property {number} height
* @property {number} ALWAYS_ON_TOP - InteractiveWindow flag for always showing a window on top
* @property {number} CLOSE_BUTTON_HIDES - InteractiveWindow flag for hiding the window instead of closing on window close by user
*/
class DesktopScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(int width READ getWidth) // Physical width of screen(s) including task bars and system menus
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL)
Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
public:
Q_INVOKABLE void setHUDAlpha(float alpha);
Q_INVOKABLE void show(const QString& path, const QString& title);
Q_INVOKABLE InteractiveWindowPointer createWindow(const QString& sourceUrl, const QVariantMap& properties = QVariantMap());
int getWidth();
int getHeight();
private:
static int flagAlwaysOnTop() { return AlwaysOnTop; }
static int flagCloseButtonHides() { return CloseButtonHides; }
Q_INVOKABLE static QVariantMap getPresentationMode();
};
#endif // hifi_DesktopScriptingInterface_h

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