diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7255e1f295..b216819ed0 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
@@ -75,6 +76,15 @@
android:enabled="true"
android:exported="false"
android:process=":breakpad_uploader"/>
+
+
+
+
+
+
diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp
index 12a7af622e..72f6cbe892 100644
--- a/android/app/src/main/cpp/native.cpp
+++ b/android/app/src/main/cpp/native.cpp
@@ -441,5 +441,51 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessU
AndroidHelper::instance().processURL(QString::fromUtf8(nativeString));
}
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_updateHifiSetting(JNIEnv *env,
+ jobject instance,
+ jstring group_,
+ jstring key_,
+ jboolean value_) {
+ const char *c_group = env->GetStringUTFChars(group_, 0);
+ const char *c_key = env->GetStringUTFChars(key_, 0);
+
+ const QString group = QString::fromUtf8(c_group);
+ const QString key = QString::fromUtf8(c_key);
+
+ env->ReleaseStringUTFChars(group_, c_group);
+ env->ReleaseStringUTFChars(key_, c_key);
+
+ bool value = value_;
+
+ Setting::Handle setting { QStringList() << group << key , !value };
+ setting.set(value);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_getHifiSettingBoolean(JNIEnv *env,
+ jobject instance,
+ jstring group_,
+ jstring key_,
+ jboolean defaultValue) {
+ const char *c_group = env->GetStringUTFChars(group_, 0);
+ const char *c_key = env->GetStringUTFChars(key_, 0);
+
+ const QString group = QString::fromUtf8(c_group);
+ const QString key = QString::fromUtf8(c_key);
+
+ env->ReleaseStringUTFChars(group_, c_group);
+ env->ReleaseStringUTFChars(key_, c_key);
+
+ Setting::Handle setting { QStringList() << group << key , defaultValue};
+ return setting.get();
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env,
+ jobject instance,
+ jboolean pluggedIn) {
+ AndroidHelper::instance().notifyHeadsetOn(pluggedIn);
+}
}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
index 94dd958600..a67eabd05a 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
@@ -13,6 +13,7 @@ package io.highfidelity.hifiinterface;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -40,6 +41,7 @@ import java.util.HashMap;
import java.util.List;
import io.highfidelity.hifiinterface.fragment.WebViewFragment;
+import io.highfidelity.hifiinterface.receiver.HeadsetStateReceiver;
/*import com.google.vr.cardboard.DisplaySynchronizer;
import com.google.vr.cardboard.DisplayUtils;
@@ -55,6 +57,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
private static final int NORMAL_DPI = 160;
private Vibrator mVibrator;
+ private HeadsetStateReceiver headsetStateReceiver;
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
@@ -152,6 +155,8 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL);
qtLayout.addView(webSlidingDrawer, layoutParams);
webSlidingDrawer.setVisibility(View.GONE);
+
+ headsetStateReceiver = new HeadsetStateReceiver();
}
@Override
@@ -162,6 +167,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
} else {
nativeEnterBackground();
}
+ unregisterReceiver(headsetStateReceiver);
//gvrApi.pauseTracking();
}
@@ -184,6 +190,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
nativeEnterForeground();
surfacesWorkaround();
keepInterfaceRunning = false;
+ registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
//gvrApi.resumeTracking();
}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
index aecbb3fc2f..451dc4ee00 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
@@ -33,9 +33,9 @@ import io.highfidelity.hifiinterface.fragment.FriendsFragment;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.LoginFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
+import io.highfidelity.hifiinterface.fragment.SettingsFragment;
import io.highfidelity.hifiinterface.fragment.SignedInFragment;
-import io.highfidelity.hifiinterface.fragment.SignupFragment;
-import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
+import io.highfidelity.hifiinterface.fragment.SignupFragment;import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginInteractionListener,
@@ -84,6 +84,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
+ updateDebugMenu(mNavigationView.getMenu());
+
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
@@ -112,6 +114,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
}
+ private void updateDebugMenu(Menu menu) {
+ if (BuildConfig.DEBUG) {
+ for (int i=0; i < menu.size(); i++) {
+ if (menu.getItem(i).getItemId() == R.id.action_debug_settings) {
+ menu.getItem(i).setVisible(true);
+ }
+ }
+ }
+ }
+
private void loadFragment(String fragment) {
switch (fragment) {
case "Login":
@@ -165,6 +177,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true);
}
+ private void loadSettingsFragment() {
+ SettingsFragment fragment = SettingsFragment.newInstance();
+
+ loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true);
+ }
+
+
private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) {
FragmentManager fragmentManager = getFragmentManager();
@@ -255,6 +274,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
case R.id.action_people:
loadPeopleFragment();
return true;
+ case R.id.action_debug_settings:
+ loadSettingsFragment();
+ return true;
}
return false;
}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
new file mode 100644
index 0000000000..cc23665e72
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
@@ -0,0 +1,63 @@
+package io.highfidelity.hifiinterface.fragment;
+
+import android.content.SharedPreferences;
+import android.media.audiofx.AcousticEchoCanceler;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.support.annotation.Nullable;
+
+import io.highfidelity.hifiinterface.R;
+
+public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+ public native void updateHifiSetting(String group, String key, boolean value);
+ public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue);
+
+ private final String HIFI_SETTINGS_ANDROID_GROUP = "Android";
+ private final String HIFI_SETTINGS_AEC_KEY = "aec";
+ private final String PREFERENCE_KEY_AEC = "aec";
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.settings);
+
+ if (!AcousticEchoCanceler.isAvailable()) {
+ getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false);
+ }
+
+ getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC,
+ getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false));
+ }
+
+ public static SettingsFragment newInstance() {
+ SettingsFragment fragment = new SettingsFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ Preference pref = findPreference(key);
+ switch (key) {
+ case "aec":
+ updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false));
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
new file mode 100644
index 0000000000..5645912d73
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
@@ -0,0 +1,18 @@
+package io.highfidelity.hifiinterface.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.util.Log;
+
+public class HeadsetStateReceiver extends BroadcastReceiver {
+
+ private native void notifyHeadsetOn(boolean pluggedIn);
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ notifyHeadsetOn(audioManager.isWiredHeadsetOn());
+ }
+}
diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml
index 3cce64f9f5..142af5d146 100644
--- a/android/app/src/main/res/menu/menu_navigation.xml
+++ b/android/app/src/main/res/menu/menu_navigation.xml
@@ -9,4 +9,9 @@
android:id="@+id/action_people"
android:title="@string/people"
/>
+
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 1d9be76774..4be4ccf867 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -39,5 +39,10 @@
tagFragmentSignup
tagFragmentPolicy
tagFragmentPeople
+ tagSettings
tagFragmentSignedIn
+ Settings
+ AEC
+ Acoustic Echo Cancellation
+ Developer
diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml
new file mode 100644
index 0000000000..5ec47b1aff
--- /dev/null
+++ b/android/app/src/main/res/xml/settings.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index a6de0d469c..aa7aa399b2 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -72,17 +72,17 @@ def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms")
-def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl.tgz'
-def qtChecksum='f312c47cd8b8dbca824c32af4eec5e66'
-def qtVersionId='nyCGcb91S4QbYeJhUkawO5x1lrLdSNB_'
+def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz'
+def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df'
+def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN'
if (Os.isFamily(Os.FAMILY_MAC)) {
- qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl.tgz'
- qtChecksum='a0c8b394aec5b0fcd46714ca3a53278a'
- qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8'
+ qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz'
+ qtChecksum='c83cc477c08a892e00c71764dca051a0'
+ qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
- qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl.tgz'
- qtChecksum='d80aed4233ce9e222aae8376e7a94bf9'
- qtVersionId='iDVXu0i3WEXRFIxQCtzcJ2XuKrE8RIqB'
+ qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz'
+ qtChecksum='0582191cc55431aa4f660848a542883e'
+ qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT'
}
def packages = [
diff --git a/interface/resources/icons/tablet-icons/people-a-msg.svg b/interface/resources/icons/tablet-icons/people-a-msg.svg
new file mode 100644
index 0000000000..862ce936ce
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/people-a-msg.svg
@@ -0,0 +1,83 @@
+
+
+
+
\ No newline at end of file
diff --git a/interface/resources/icons/tablet-icons/people-i-msg.svg b/interface/resources/icons/tablet-icons/people-i-msg.svg
new file mode 100644
index 0000000000..635a01be4b
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/people-i-msg.svg
@@ -0,0 +1,24 @@
+
+
+
diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml
index 35ed3799a6..b1900cf0a7 100644
--- a/interface/resources/qml/AnimStats.qml
+++ b/interface/resources/qml/AnimStats.qml
@@ -48,35 +48,11 @@ Item {
spacing: 4; x: 4; y: 4;
StatText {
- text: "State Machines:---------------------------------------------------------------------------"
+ text: root.positionText
}
- ListView {
- width: firstCol.width
- height: root.animStateMachines.length * 15
- visible: root.animStateMchines.length > 0;
- model: root.animStateMachines
- delegate: StatText {
- text: {
- return modelData;
- }
- }
- }
- }
- }
-
- Rectangle {
- width: secondCol.width + 8
- height: secondCol.height + 8
- color: root.bgColor;
-
- Column {
- id: secondCol
- spacing: 4; x: 4; y: 4;
-
StatText {
text: "Anim Vars:--------------------------------------------------------------------------------"
}
-
ListView {
width: secondCol.width
height: root.animVars.length * 15
@@ -104,6 +80,36 @@ Item {
}
}
+ Rectangle {
+ width: secondCol.width + 8
+ height: secondCol.height + 8
+ color: root.bgColor;
+
+ Column {
+ id: secondCol
+ spacing: 4; x: 4; y: 4;
+
+ StatText {
+ text: root.rotationText
+ }
+ StatText {
+ text: "State Machines:---------------------------------------------------------------------------"
+ }
+ ListView {
+ width: firstCol.width
+ height: root.animStateMachines.length * 15
+ visible: root.animStateMachines.length > 0;
+ model: root.animStateMachines
+ delegate: StatText {
+ text: {
+ return modelData;
+ }
+ }
+ }
+
+ }
+ }
+
Rectangle {
width: thirdCol.width + 8
height: thirdCol.height + 8
@@ -113,10 +119,12 @@ Item {
id: thirdCol
spacing: 4; x: 4; y: 4;
+ StatText {
+ text: root.velocityText
+ }
StatText {
text: "Alpha Values:--------------------------------------------------------------------------"
}
-
ListView {
width: thirdCol.width
height: root.animAlphaValues.length * 15
diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index 35a0078d32..e8fc41da63 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -271,6 +271,8 @@ Rectangle {
connectionsUserModel.getFirstPage();
}
activeTab = "connectionsTab";
+ connectionsOnlineDot.visible = false;
+ pal.sendToScript({method: 'hideNotificationDot'});
connectionsHelpText.color = hifi.colors.blueAccent;
}
}
@@ -298,6 +300,16 @@ Rectangle {
}
}
}
+ Rectangle {
+ id: connectionsOnlineDot;
+ visible: false;
+ width: 10;
+ height: width;
+ radius: width;
+ color: "#EF3B4E"
+ anchors.left: parent.left;
+ anchors.verticalCenter: parent.verticalCenter;
+ }
// "CONNECTIONS" text
RalewaySemiBold {
id: connectionsTabSelectorText;
@@ -305,7 +317,11 @@ Rectangle {
// Text size
size: hifi.fontSizes.tabularData;
// Anchors
- anchors.fill: parent;
+ anchors.left: connectionsOnlineDot.visible ? connectionsOnlineDot.right : parent.left;
+ anchors.leftMargin: connectionsOnlineDot.visible ? 4 : 0;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.right: parent.right;
// Style
font.capitalization: Font.AllUppercase;
color: activeTab === "connectionsTab" ? hifi.colors.blueAccent : hifi.colors.baseGray;
@@ -326,7 +342,7 @@ Rectangle {
anchors.left: connectionsTabSelectorTextContainer.left;
anchors.top: connectionsTabSelectorTextContainer.top;
anchors.topMargin: 1;
- anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42;
+ anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42 + connectionsOnlineDot.width + connectionsTabSelectorText.anchors.leftMargin;
RalewayRegular {
id: connectionsHelpText;
text: "[?]";
@@ -1267,6 +1283,9 @@ Rectangle {
case 'http.response':
http.handleHttpResponse(message);
break;
+ case 'changeConnectionsDotStatus':
+ connectionsOnlineDot.visible = message.shouldShowDot;
+ break;
default:
console.log('Unrecognized message:', JSON.stringify(message));
}
diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
new file mode 100644
index 0000000000..8f391f24c0
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -0,0 +1,290 @@
+//
+// marketplaceItemTester
+// qml/hifi/commerce/marketplaceItemTester
+//
+// Load items not in the marketplace for testing purposes
+//
+// Created by Zach Fox on 2018-09-05
+// 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.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Dialogs 1.0
+import QtQuick.Layouts 1.1
+import Hifi 1.0 as Hifi
+import "../../../styles-uit" as HifiStylesUit
+import "../../../controls-uit" as HifiControlsUit
+
+
+
+Rectangle {
+ id: root
+
+ property string installedApps
+ property var nextResourceObjectId: 0
+ signal sendToScript(var message)
+
+ HifiStylesUit.HifiConstants { id: hifi }
+ ListModel { id: resourceListModel }
+
+ color: hifi.colors.white
+
+ AnimatedImage {
+ id: spinner;
+ source: "spinner.gif"
+ width: 74;
+ height: width;
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ }
+
+ function fromScript(message) {
+ switch (message.method) {
+ case "newResourceObjectInTest":
+ var resourceObject = message.resourceObject;
+ resourceListModel.append(resourceObject);
+ spinner.visible = false;
+ break;
+ case "nextObjectIdInTest":
+ nextResourceObjectId = message.id;
+ spinner.visible = false;
+ break;
+ }
+ }
+
+ function buildResourceObj(resource) {
+ resource = resource.trim();
+ var assetType = (resource.match(/\.app\.json$/) ? "application" :
+ resource.match(/\.fst$/) ? "avatar" :
+ resource.match(/\.json\.gz$/) ? "content set" :
+ resource.match(/\.json$/) ? "entity or wearable" :
+ "unknown");
+ return { "id": nextResourceObjectId++,
+ "resource": resource,
+ "assetType": assetType };
+ }
+
+ function installResourceObj(resourceObj) {
+ if ("application" === resourceObj.assetType) {
+ Commerce.installApp(resourceObj.resource);
+ }
+ }
+
+ function addAllInstalledAppsToList() {
+ var i, apps = Commerce.getInstalledApps().split(","), len = apps.length;
+ for(i = 0; i < len - 1; ++i) {
+ if (i in apps) {
+ resourceListModel.append(buildResourceObj(apps[i]));
+ }
+ }
+ }
+
+ function toUrl(resource) {
+ var httpPattern = /^http/i;
+ return httpPattern.test(resource) ? resource : "file:///" + resource;
+ }
+
+ function rezEntity(resource, entityType) {
+ sendToScript({
+ method: 'tester_rezClicked',
+ itemHref: toUrl(resource),
+ itemType: entityType});
+ }
+
+ ListView {
+ anchors.fill: parent
+ anchors.leftMargin: 12
+ anchors.bottomMargin: 40
+ anchors.rightMargin: 12
+ model: resourceListModel
+ spacing: 5
+ interactive: false
+
+ delegate: RowLayout {
+ anchors.left: parent.left
+ width: parent.width
+ spacing: 5
+
+ property var actions: {
+ "forward": function(resource, assetType){
+ switch(assetType) {
+ case "application":
+ Commerce.openApp(resource);
+ break;
+ case "avatar":
+ MyAvatar.useFullAvatarURL(resource);
+ break;
+ case "content set":
+ urlHandler.handleUrl("hifi://localhost/0,0,0");
+ Commerce.replaceContentSet(toUrl(resource), "");
+ break;
+ case "entity":
+ case "wearable":
+ rezEntity(resource, assetType);
+ break;
+ default:
+ print("Marketplace item tester unsupported assetType " + assetType);
+ }
+ },
+ "trash": function(){
+ if ("application" === assetType) {
+ Commerce.uninstallApp(resource);
+ }
+ sendToScript({
+ method: "tester_deleteResourceObject",
+ objectId: resourceListModel.get(index).id});
+ resourceListModel.remove(index);
+ }
+ }
+
+ Column {
+ Layout.preferredWidth: root.width * .6
+ spacing: 5
+ Text {
+ text: {
+ var match = resource.match(/\/([^/]*)$/);
+ return match ? match[1] : resource;
+ }
+ font.pointSize: 12
+ horizontalAlignment: Text.AlignBottom
+ }
+ Text {
+ text: resource
+ font.pointSize: 8
+ width: root.width * .6
+ horizontalAlignment: Text.AlignBottom
+ wrapMode: Text.WrapAnywhere
+ }
+ }
+
+ ComboBox {
+ id: comboBox
+
+ Layout.preferredWidth: root.width * .2
+
+ model: [
+ "application",
+ "avatar",
+ "content set",
+ "entity",
+ "wearable",
+ "unknown"
+ ]
+
+ currentIndex: (("entity or wearable" === assetType) ?
+ model.indexOf("unknown") : model.indexOf(assetType))
+
+ Component.onCompleted: {
+ onCurrentIndexChanged.connect(function() {
+ assetType = model[currentIndex];
+ sendToScript({
+ method: "tester_updateResourceObjectAssetType",
+ objectId: resourceListModel.get(index)["id"],
+ assetType: assetType });
+ });
+ }
+ }
+
+ Repeater {
+ model: [ "forward", "trash" ]
+
+ HifiStylesUit.HiFiGlyphs {
+ property var glyphs: {
+ "application": hifi.glyphs.install,
+ "avatar": hifi.glyphs.avatar,
+ "content set": hifi.glyphs.globe,
+ "entity": hifi.glyphs.wand,
+ "trash": hifi.glyphs.trash,
+ "unknown": hifi.glyphs.circleSlash,
+ "wearable": hifi.glyphs.hat,
+ }
+ text: (("trash" === modelData) ?
+ glyphs.trash :
+ glyphs[comboBox.model[comboBox.currentIndex]])
+ size: ("trash" === modelData) ? 22 : 30
+ color: hifi.colors.black
+ horizontalAlignment: Text.AlignHCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ actions[modelData](resource, comboBox.currentText);
+ }
+ }
+ }
+ }
+ }
+
+ headerPositioning: ListView.OverlayHeader
+ header: HifiStylesUit.RalewayRegular {
+ id: rootHeader
+ text: "Marketplace Item Tester"
+ height: 80
+ width: paintedWidth
+ size: 22
+ color: hifi.colors.black
+ anchors.left: parent.left
+ anchors.leftMargin: 12
+ }
+
+ footerPositioning: ListView.OverlayFooter
+ footer: Row {
+ id: rootActions
+ spacing: 20
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ property string currentAction
+ property var actions: {
+ "Load File": function(){
+ rootActions.currentAction = "load file";
+ Window.browseChanged.connect(onResourceSelected);
+ Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)");
+ },
+ "Load URL": function(){
+ rootActions.currentAction = "load url";
+ Window.promptTextChanged.connect(onResourceSelected);
+ Window.promptAsync("Please enter a URL", "");
+ }
+ }
+
+ function onResourceSelected(resource) {
+ // It is possible that we received the present signal
+ // from something other than our browserAsync window.
+ // Alas, there is nothing we can do about that so charge
+ // ahead as though we are sure the present signal is one
+ // we expect.
+ switch(currentAction) {
+ case "load file":
+ Window.browseChanged.disconnect(onResourceSelected);
+ break
+ case "load url":
+ Window.promptTextChanged.disconnect(onResourceSelected);
+ break;
+ }
+ if (resource) {
+ var resourceObj = buildResourceObj(resource);
+ installResourceObj(resourceObj);
+ sendToScript({
+ method: 'tester_newResourceObject',
+ resourceObject: resourceObj });
+ }
+ }
+
+ Repeater {
+ model: [ "Load File", "Load URL" ]
+ HifiControlsUit.Button {
+ color: hifi.buttons.blue
+ fontSize: 20
+ text: modelData
+ width: root.width / 3
+ height: 40
+ onClicked: actions[text]()
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif
new file mode 100644
index 0000000000..00f75ae62f
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif differ
diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
index 032d9b0199..eeb9ac3c54 100644
--- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
@@ -59,7 +59,7 @@ Item {
Connections {
target: Commerce;
-
+
onContentSetChanged: {
if (contentSetHref === root.itemHref) {
showConfirmation = true;
@@ -135,7 +135,7 @@ Item {
anchors.topMargin: 8;
width: 30;
height: width;
-
+
HiFiGlyphs {
id: closeContextMenuGlyph;
text: hifi.glyphs.close;
@@ -376,7 +376,7 @@ Item {
}
}
}
-
+
transform: Rotation {
id: rotation;
origin.x: flipable.width/2;
@@ -509,7 +509,7 @@ Item {
}
verticalAlignment: Text.AlignTop;
}
-
+
HiFiGlyphs {
id: statusIcon;
text: {
@@ -588,7 +588,7 @@ Item {
border.width: 1;
border.color: "#E2334D";
}
-
+
HiFiGlyphs {
id: contextMenuGlyph;
text: hifi.glyphs.verticalEllipsis;
@@ -615,7 +615,7 @@ Item {
}
}
}
-
+
Rectangle {
id: rezzedNotifContainer;
z: 998;
@@ -663,13 +663,13 @@ Item {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
-
+
onFocusChanged: {
if (focus) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
-
+
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
if (root.itemType === "contentSet") {
@@ -775,7 +775,7 @@ Item {
// Style
color: hifi.colors.redAccent;
horizontalAlignment: Text.AlignRight;
-
+
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index 65d98af234..47b9d354d0 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -408,9 +408,7 @@ Rectangle {
Connections {
onSendSignalToWallet: {
- if (msg.method === 'walletReset' || msg.method === 'passphraseReset') {
- sendToScript(msg);
- } else if (msg.method === 'walletSecurity_changeSecurityImage') {
+ if (msg.method === 'walletSecurity_changeSecurityImage') {
securityImageChange.initModel();
root.activeView = "securityImageChange";
}
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index e1345ecc28..b05c7b4786 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -11,6 +11,7 @@
#include "AndroidHelper.h"
#include
#include
+#include
#include "Application.h"
#if defined(qApp)
@@ -19,6 +20,7 @@
#define qApp (static_cast(QCoreApplication::instance()))
AndroidHelper::AndroidHelper() {
+ qRegisterMetaType("QAudio::Mode");
}
AndroidHelper::~AndroidHelper() {
@@ -58,6 +60,15 @@ void AndroidHelper::processURL(const QString &url) {
}
}
+void AndroidHelper::notifyHeadsetOn(bool pluggedIn) {
+#if defined (Q_OS_ANDROID)
+ auto audioClient = DependencyManager::get();
+ if (audioClient) {
+ QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn));
+ }
+#endif
+}
+
void AndroidHelper::signup(QString email, QString username, QString password) {
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
@@ -131,4 +142,3 @@ void AndroidHelper::signupFailed(QNetworkReply* reply) {
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
}
}
-
diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h
index aec1907801..1f4bac4ad8 100644
--- a/interface/src/AndroidHelper.h
+++ b/interface/src/AndroidHelper.h
@@ -31,6 +31,7 @@ public:
void performHapticFeedback(int duration);
void processURL(const QString &url);
+ void notifyHeadsetOn(bool pluggedIn);
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 46cebc1661..3a34c87ef1 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1691,21 +1691,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return DependencyManager::get()->navigationFocused() ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float {
-#if defined(Q_OS_WIN)
+#if defined(Q_OS_WIN)
return 1;
#else
return 0;
#endif
});
_applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float {
-#if defined(Q_OS_MAC)
+#if defined(Q_OS_MAC)
return 1;
#else
return 0;
#endif
});
_applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float {
-#if defined(Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID)
return 1;
#else
return 0;
@@ -1759,10 +1759,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
-
QTimer* settingsTimer = new QTimer();
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
- connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
+ // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the
+ // receiver object, otherwise it will run on the application thread and trigger a warning
+ // about trying to kill the timer on the main thread.
+ connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{
// Disconnect the signal from the save settings
QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
// Stop the settings timer
@@ -2881,9 +2883,10 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/common/CommerceLightbox.qml" },
QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" },
QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" },
- QUrl{ "hifi/commerce/common/SortableListModel.qml" },
QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" },
+ QUrl{ "hifi/commerce/common/SortableListModel.qml" },
QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" },
+ QUrl{ "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"},
QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" },
QUrl{ "hifi/commerce/purchases/Purchases.qml" },
QUrl{ "hifi/commerce/wallet/Help.qml" },
@@ -3498,13 +3501,14 @@ bool Application::isServerlessMode() const {
}
void Application::setIsInterstitialMode(bool interstitialMode) {
- Settings settings;
- bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool();
- if (_interstitialMode != interstitialMode && enableInterstitial) {
- _interstitialMode = interstitialMode;
+ bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled();
+ if (enableInterstitial) {
+ if (_interstitialMode != interstitialMode) {
+ _interstitialMode = interstitialMode;
- DependencyManager::get()->setAudioPaused(_interstitialMode);
- DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode);
+ DependencyManager::get()->setAudioPaused(_interstitialMode);
+ DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode);
+ }
}
}
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 3bebc60480..eedbdb7622 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -432,7 +432,7 @@ public slots:
void setIsServerlessMode(bool serverlessDomain);
void loadServerlessDomain(QUrl domainURL, bool errorDomain = false);
- void setIsInterstitialMode(bool interstialMode);
+ void setIsInterstitialMode(bool interstitialMode);
void updateVerboseLogging();
diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp
index 3c85cfb339..e86061b090 100644
--- a/interface/src/ConnectionMonitor.cpp
+++ b/interface/src/ConnectionMonitor.cpp
@@ -41,9 +41,15 @@ void ConnectionMonitor::init() {
}
connect(&_timer, &QTimer::timeout, this, [this]() {
- qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
// set in a timeout error
- emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5);
+ bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled();
+ if (enableInterstitial) {
+ qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
+ emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5);
+ } else {
+ qDebug() << "ConnectionMonitor: Showing connection failure window";
+ DependencyManager::get()->setDomainConnectionFailureVisibility(true);
+ }
});
}
@@ -53,4 +59,8 @@ void ConnectionMonitor::startTimer() {
void ConnectionMonitor::stopTimer() {
_timer.stop();
+ bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled();
+ if (!enableInterstitial) {
+ DependencyManager::get()->setDomainConnectionFailureVisibility(false);
+ }
}
diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h
index 5e75e2618b..2fda6ef7cd 100644
--- a/interface/src/ConnectionMonitor.h
+++ b/interface/src/ConnectionMonitor.h
@@ -24,7 +24,7 @@ public:
void init();
signals:
- void setRedirectErrorState(QUrl errorURL, int reasonCode);
+ void setRedirectErrorState(QUrl errorURL, QString reasonMessage = "", int reasonCode = -1, const QString& extraInfo = "");
private slots:
void startTimer();
@@ -34,4 +34,4 @@ private:
QTimer _timer;
};
-#endif // hifi_ConnectionMonitor_h
\ No newline at end of file
+#endif // hifi_ConnectionMonitor_h
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 1faf17ea9a..0fdd246d7b 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -456,31 +456,37 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
}
void AvatarManager::clearOtherAvatars() {
- // Remove other avatars from the world but don't actually remove them from _avatarHash
- // each will either be removed on timeout or will re-added to the world on receipt of update.
- const render::ScenePointer& scene = qApp->getMain3DScene();
- render::Transaction transaction;
-
- QReadLocker locker(&_hashLock);
- AvatarHash::iterator avatarIterator = _avatarHash.begin();
- while (avatarIterator != _avatarHash.end()) {
- auto avatar = std::static_pointer_cast(avatarIterator.value());
- if (avatar != _myAvatar) {
- handleRemovedAvatar(avatar);
- avatarIterator = _avatarHash.erase(avatarIterator);
- } else {
- ++avatarIterator;
- }
- }
- assert(scene);
- scene->enqueueTransaction(transaction);
_myAvatar->clearLookAtTargetAvatar();
+
+ // setup a vector of removed avatars outside the scope of the hash lock
+ std::vector removedAvatars;
+
+ {
+ QWriteLocker locker(&_hashLock);
+
+ removedAvatars.reserve(_avatarHash.size());
+
+ auto avatarIterator = _avatarHash.begin();
+ while (avatarIterator != _avatarHash.end()) {
+ auto avatar = std::static_pointer_cast(avatarIterator.value());
+ if (avatar != _myAvatar) {
+ removedAvatars.push_back(avatar);
+ avatarIterator = _avatarHash.erase(avatarIterator);
+ } else {
+ ++avatarIterator;
+ }
+ }
+ }
+
+ for (auto& av : removedAvatars) {
+ handleRemovedAvatar(av);
+ }
}
void AvatarManager::deleteAllAvatars() {
assert(_avatarsToChangeInPhysics.empty());
- QReadLocker locker(&_hashLock);
+ QWriteLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast(avatarIterator.value());
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 306ba6f39b..9c4287728d 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -204,7 +204,12 @@ private:
void simulateAvatarFades(float deltaTime);
AvatarSharedPointer newSharedAvatar() override;
- void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
+
+ // called only from the AvatarHashMap thread - cannot be called while this thread holds the
+ // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree
+ // frequently grabs a read lock on the hash to get a given avatar by ID
+ void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar,
+ KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
QVector _avatarsToFade;
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index c47cfdb383..df7ec93b6a 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -91,6 +91,8 @@ const float MIN_SCALE_CHANGED_DELTA = 0.001f;
const int MODE_READINGS_RING_BUFFER_SIZE = 500;
const float CENTIMETERS_PER_METER = 100.0f;
+const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" };
+
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
_yawSpeed(YAW_SPEED_DEFAULT),
@@ -118,7 +120,22 @@ MyAvatar::MyAvatar(QThread* thread) :
_goToOrientation(),
_prevShouldDrawHead(true),
_audioListenerMode(FROM_HEAD),
- _hmdAtRestDetector(glm::vec3(0), glm::quat())
+ _hmdAtRestDetector(glm::vec3(0), glm::quat()),
+ _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND),
+ _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f),
+ _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale),
+ _yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed),
+ _pitchSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "pitchSpeed", _pitchSpeed),
+ _fullAvatarURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarURL",
+ AvatarData::defaultFullAvatarModelUrl()),
+ _fullAvatarModelNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarModelName", _fullAvatarModelName),
+ _animGraphURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "animGraphURL", QUrl("")),
+ _displayNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "displayName", ""),
+ _collisionSoundURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "collisionSoundURL", QUrl(_collisionSoundURL)),
+ _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
+ _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
+ _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
+ _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", _flyingPrefHMD)
{
_clientTraitsHandler = std::unique_ptr(new ClientTraitsHandler(this));
@@ -1135,88 +1152,80 @@ void MyAvatar::restoreRoleAnimation(const QString& role) {
}
void MyAvatar::saveAvatarUrl() {
- Settings settings;
- settings.beginGroup("Avatar");
- if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) {
- settings.setValue("fullAvatarURL",
- _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
- "" :
- _fullAvatarURLFromPreferences.toString());
+ if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid()) {
+ _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
+ "" :
+ _fullAvatarURLFromPreferences.toString());
+ }
+}
+
+void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) {
+ // The original Settings interface saved avatar-entity array data like this:
+ // Avatar/avatarEntityData/size: 5
+ // Avatar/avatarEntityData/1/id: ...
+ // Avatar/avatarEntityData/1/properties: ...
+ // ...
+ // Avatar/avatarEntityData/5/id: ...
+ // Avatar/avatarEntityData/5/properties: ...
+ //
+ // Create Setting::Handles to mimic this.
+
+ while (_avatarEntityIDSettings.size() <= avatarEntityIndex) {
+ Setting::Handle idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
+ << QString::number(avatarEntityIndex + 1) << "id", QUuid());
+ _avatarEntityIDSettings.push_back(idHandle);
+ Setting::Handle dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
+ << QString::number(avatarEntityIndex + 1) << "properties", QByteArray());
+ _avatarEntityDataSettings.push_back(dataHandle);
}
- settings.endGroup();
}
void MyAvatar::saveData() {
- Settings settings;
- settings.beginGroup("Avatar");
-
- settings.setValue("dominantHand", _dominantHand);
- settings.setValue("headPitch", getHead()->getBasePitch());
-
- settings.setValue("scale", _targetScale);
-
- settings.setValue("yawSpeed", _yawSpeed);
- settings.setValue("pitchSpeed", _pitchSpeed);
+ _dominantHandSetting.set(_dominantHand);
+ _headPitchSetting.set(getHead()->getBasePitch());
+ _scaleSetting.set(_targetScale);
+ _yawSpeedSetting.set(_yawSpeed);
+ _pitchSpeedSetting.set(_pitchSpeed);
// only save the fullAvatarURL if it has not been overwritten on command line
// (so the overrideURL is not valid), or it was overridden _and_ we specified
// --replaceAvatarURL (so _saveAvatarOverrideUrl is true)
if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) {
- settings.setValue("fullAvatarURL",
- _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
- "" :
- _fullAvatarURLFromPreferences.toString());
+ _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
+ "" :
+ _fullAvatarURLFromPreferences.toString());
}
- settings.setValue("fullAvatarModelName", _fullAvatarModelName);
-
+ _fullAvatarModelNameSetting.set(_fullAvatarModelName);
QUrl animGraphUrl = _prefOverrideAnimGraphUrl.get();
- settings.setValue("animGraphURL", animGraphUrl);
+ _animGraphURLSetting.set(animGraphUrl);
+ _displayNameSetting.set(_displayName);
+ _collisionSoundURLSetting.set(_collisionSoundURL);
+ _useSnapTurnSetting.set(_useSnapTurn);
+ _userHeightSetting.set(getUserHeight());
+ _flyingHMDSetting.set(getFlyingHMDPref());
- settings.beginWriteArray("attachmentData");
- for (int i = 0; i < _attachmentData.size(); i++) {
- settings.setArrayIndex(i);
- const AttachmentData& attachment = _attachmentData.at(i);
- settings.setValue("modelURL", attachment.modelURL);
- settings.setValue("jointName", attachment.jointName);
- settings.setValue("translation_x", attachment.translation.x);
- settings.setValue("translation_y", attachment.translation.y);
- settings.setValue("translation_z", attachment.translation.z);
- glm::vec3 eulers = safeEulerAngles(attachment.rotation);
- settings.setValue("rotation_x", eulers.x);
- settings.setValue("rotation_y", eulers.y);
- settings.setValue("rotation_z", eulers.z);
- settings.setValue("scale", attachment.scale);
- settings.setValue("isSoft", attachment.isSoft);
- }
- settings.endArray();
-
- settings.remove("avatarEntityData");
- settings.beginWriteArray("avatarEntityData");
- int avatarEntityIndex = 0;
auto hmdInterface = DependencyManager::get();
_avatarEntitiesLock.withReadLock([&] {
- for (auto entityID : _avatarEntityData.keys()) {
- if (hmdInterface->getCurrentTabletFrameID() == entityID) {
- // don't persist the tablet between domains / sessions
- continue;
- }
+ QList avatarEntityIDs = _avatarEntityData.keys();
+ unsigned int avatarEntityCount = avatarEntityIDs.size();
+ unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0);
+ resizeAvatarEntitySettingHandles(std::max(avatarEntityCount, previousAvatarEntityCount));
+ _avatarEntityCountSetting.set(avatarEntityCount);
- settings.setArrayIndex(avatarEntityIndex);
- settings.setValue("id", entityID);
- settings.setValue("properties", _avatarEntityData.value(entityID));
+ unsigned int avatarEntityIndex = 0;
+ for (auto entityID : avatarEntityIDs) {
+ _avatarEntityIDSettings[avatarEntityIndex].set(entityID);
+ _avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID));
avatarEntityIndex++;
}
+
+ // clean up any left-over (due to the list shrinking) slots
+ for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) {
+ _avatarEntityIDSettings[avatarEntityIndex].remove();
+ _avatarEntityDataSettings[avatarEntityIndex].remove();
+ }
});
- settings.endArray();
-
- settings.setValue("displayName", _displayName);
- settings.setValue("collisionSoundURL", _collisionSoundURL);
- settings.setValue("useSnapTurn", _useSnapTurn);
- settings.setValue("userHeight", getUserHeight());
- settings.setValue("flyingHMD", getFlyingHMDPref());
-
- settings.endGroup();
}
float loadSetting(Settings& settings, const QString& name, float defaultValue) {
@@ -1314,66 +1323,36 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
}
void MyAvatar::loadData() {
- Settings settings;
- settings.beginGroup("Avatar");
+ getHead()->setBasePitch(_headPitchSetting.get());
- getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
+ _yawSpeed = _yawSpeedSetting.get(_yawSpeed);
+ _pitchSpeed = _pitchSpeedSetting.get(_pitchSpeed);
- _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed);
- _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed);
-
- _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString()));
- _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl();
- _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
+ _prefOverrideAnimGraphUrl.set(_prefOverrideAnimGraphUrl.get().toString());
+ _fullAvatarURLFromPreferences = _fullAvatarURLSetting.get(QUrl(AvatarData::defaultFullAvatarModelUrl()));
+ _fullAvatarModelName = _fullAvatarModelNameSetting.get(DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
- int attachmentCount = settings.beginReadArray("attachmentData");
- for (int i = 0; i < attachmentCount; i++) {
- settings.setArrayIndex(i);
- AttachmentData attachment;
- attachment.modelURL = settings.value("modelURL").toUrl();
- attachment.jointName = settings.value("jointName").toString();
- attachment.translation.x = loadSetting(settings, "translation_x", 0.0f);
- attachment.translation.y = loadSetting(settings, "translation_y", 0.0f);
- attachment.translation.z = loadSetting(settings, "translation_z", 0.0f);
- glm::vec3 eulers;
- eulers.x = loadSetting(settings, "rotation_x", 0.0f);
- eulers.y = loadSetting(settings, "rotation_y", 0.0f);
- eulers.z = loadSetting(settings, "rotation_z", 0.0f);
- attachment.rotation = glm::quat(eulers);
- attachment.scale = loadSetting(settings, "scale", 1.0f);
- attachment.isSoft = settings.value("isSoft").toBool();
- // old attachments are stored and loaded/converted later when rig is ready
- _oldAttachmentData.append(attachment);
- }
- settings.endArray();
-
- int avatarEntityCount = settings.beginReadArray("avatarEntityData");
+ int avatarEntityCount = _avatarEntityCountSetting.get(0);
for (int i = 0; i < avatarEntityCount; i++) {
- settings.setArrayIndex(i);
- QUuid entityID = settings.value("id").toUuid();
+ resizeAvatarEntitySettingHandles(i);
// QUuid entityID = QUuid::createUuid(); // generate a new ID
- QByteArray properties = settings.value("properties").toByteArray();
+ QUuid entityID = _avatarEntityIDSettings[i].get(QUuid());
+ QByteArray properties = _avatarEntityDataSettings[i].get();
updateAvatarEntity(entityID, properties);
}
- settings.endArray();
- if (avatarEntityCount == 0) {
- // HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file
- settings.remove("avatarEntityData");
- }
// Flying preferences must be loaded before calling setFlyingEnabled()
Setting::Handle firstRunVal { Settings::firstRun, true };
- setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool());
+ setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get());
setFlyingEnabled(getFlyingEnabled());
- setDisplayName(settings.value("displayName").toString());
- setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
- setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
- setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower());
- setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble());
- settings.endGroup();
+ setDisplayName(_displayNameSetting.get());
+ setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
+ setSnapTurn(_useSnapTurnSetting.get());
+ setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
+ setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
@@ -1442,6 +1421,7 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString&
return attachment;
}
+
int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) {
qCDebug(interfaceapp) << "Error: ignoring update packet for MyAvatar"
<< " packetLength = " << buffer.size();
@@ -2899,10 +2879,7 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
}
// Set avatar current scale
- Settings settings;
- settings.beginGroup("Avatar");
- _targetScale = loadSetting(settings, "scale", 1.0f);
-
+ _targetScale = _scaleSetting.get();
// clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate. Because
// this might cause our avatar to become embedded in the terrain.
_targetScale = getDomainLimitedScale();
@@ -2914,7 +2891,6 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
setModelScale(_targetScale);
rebuildCollisionShape();
- settings.endGroup();
_haveReceivedHeightLimitsFromDomain = true;
}
@@ -2925,10 +2901,7 @@ void MyAvatar::leaveDomain() {
}
void MyAvatar::saveAvatarScale() {
- Settings settings;
- settings.beginGroup("Avatar");
- settings.setValue("scale", _targetScale);
- settings.endGroup();
+ _scaleSetting.set(_targetScale);
}
void MyAvatar::clearScaleRestriction() {
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 139f1f6ea2..1dc0b3cd40 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -550,6 +550,7 @@ public:
float getHMDRollControlRate() const { return _hmdRollControlRate; }
// get/set avatar data
+ void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex);
void saveData();
void loadData();
@@ -1806,6 +1807,23 @@ private:
bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 };
+
+ Setting::Handle _dominantHandSetting;
+ Setting::Handle _headPitchSetting;
+ Setting::Handle _scaleSetting;
+ Setting::Handle _yawSpeedSetting;
+ Setting::Handle _pitchSpeedSetting;
+ Setting::Handle _fullAvatarURLSetting;
+ Setting::Handle _fullAvatarModelNameSetting;
+ Setting::Handle _animGraphURLSetting;
+ Setting::Handle _displayNameSetting;
+ Setting::Handle _collisionSoundURLSetting;
+ Setting::Handle _useSnapTurnSetting;
+ Setting::Handle _userHeightSetting;
+ Setting::Handle _flyingHMDSetting;
+ Setting::Handle _avatarEntityCountSetting;
+ std::vector> _avatarEntityIDSettings;
+ std::vector> _avatarEntityDataSettings;
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 06da18148f..7c5df0f3e3 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -199,12 +199,18 @@ void QmlCommerce::transferAssetToUsername(const QString& username,
}
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
- auto ledger = DependencyManager::get();
- ledger->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true);
+ if (!certificateID.isEmpty()) {
+ auto ledger = DependencyManager::get();
+ ledger->updateLocation(
+ certificateID,
+ DependencyManager::get()->getPlaceName(),
+ true);
+ }
qApp->replaceDomainContent(itemHref);
- QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } };
+ QJsonObject messageProperties = {
+ { "status", "SuccessfulRequestToReplaceContent" },
+ { "content_set_url", itemHref } };
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
-
emit contentSetChanged(itemHref);
}
@@ -228,6 +234,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
// Thus, we protect against deleting the .app.json from the user's disk (below)
// by skipping that check for the app we just installed.
if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) {
+ installedAppsFromMarketplace += appFileName + ",";
continue;
}
diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp
index 888b3ddbe8..ad698c409b 100644
--- a/interface/src/raypick/ParabolaPointer.cpp
+++ b/interface/src/raypick/ParabolaPointer.cpp
@@ -382,9 +382,8 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() {
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
- gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola);
-
{
+ gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola);
auto state = std::make_shared();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(false,
@@ -396,6 +395,7 @@ const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::get
}
{
+ gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola_translucent);
auto state = std::make_shared();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(true,
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index e3ae65aee1..d4eb37e0aa 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -180,6 +180,14 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc
Setting::Handle(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location);
}
+bool WindowScriptingInterface::getInterstitialModeEnabled() const {
+ return DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled();
+}
+
+void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitialMode) {
+ DependencyManager::get()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode);
+}
+
bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
auto offscreenUi = DependencyManager::get();
return offscreenUi->isPointOnDesktopWindow(point);
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index 3827406729..ddd7159f23 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -49,6 +49,7 @@ class WindowScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(int innerHeight READ getInnerHeight)
Q_PROPERTY(int x READ getX)
Q_PROPERTY(int y READ getY)
+ Q_PROPERTY(bool interstitialModeEnabled READ getInterstitialModeEnabled WRITE setInterstitialModeEnabled)
public:
WindowScriptingInterface();
@@ -758,6 +759,9 @@ private:
QString getPreviousBrowseAssetLocation() const;
void setPreviousBrowseAssetLocation(const QString& location);
+ bool getInterstitialModeEnabled() const;
+ void setInterstitialModeEnabled(bool enableInterstitialMode);
+
void ensureReticleVisible() const;
int createMessageBox(QString title, QString text, int buttons, int defaultButton);
diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp
index 7b4778e365..e3579fa2dc 100644
--- a/interface/src/ui/AnimStats.cpp
+++ b/interface/src/ui/AnimStats.cpp
@@ -42,6 +42,29 @@ void AnimStats::updateStats(bool force) {
auto myAvatar = avatarManager->getMyAvatar();
auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap();
+ glm::vec3 position = myAvatar->getWorldPosition();
+ glm::quat rotation = myAvatar->getWorldOrientation();
+ glm::vec3 velocity = myAvatar->getWorldVelocity();
+
+ _positionText = QString("Position: (%1, %2, %3)").
+ arg(QString::number(position.x, 'f', 2)).
+ arg(QString::number(position.y, 'f', 2)).
+ arg(QString::number(position.z, 'f', 2));
+ emit positionTextChanged();
+
+ glm::vec3 eulerRotation = safeEulerAngles(rotation);
+ _rotationText = QString("Heading: %1").
+ arg(QString::number(glm::degrees(eulerRotation.y), 'f', 2));
+ emit rotationTextChanged();
+
+ // transform velocity into rig coordinate frame. z forward.
+ glm::vec3 localVelocity = Quaternions::Y_180 * glm::inverse(rotation) * velocity;
+ _velocityText = QString("Local Vel: (%1, %2, %3)").
+ arg(QString::number(localVelocity.x, 'f', 2)).
+ arg(QString::number(localVelocity.y, 'f', 2)).
+ arg(QString::number(localVelocity.z, 'f', 2));
+ emit velocityTextChanged();
+
// update animation debug alpha values
QStringList newAnimAlphaValues;
qint64 now = usecTimestampNow();
diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h
index 1a6795b498..7b6aaf7b54 100644
--- a/interface/src/ui/AnimStats.h
+++ b/interface/src/ui/AnimStats.h
@@ -19,6 +19,9 @@ class AnimStats : public QQuickItem {
Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged)
Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged)
Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged)
+ Q_PROPERTY(QString positionText READ positionText NOTIFY positionTextChanged)
+ Q_PROPERTY(QString rotationText READ rotationText NOTIFY rotationTextChanged)
+ Q_PROPERTY(QString velocityText READ velocityText NOTIFY velocityTextChanged)
public:
static AnimStats* getInstance();
@@ -27,9 +30,13 @@ public:
void updateStats(bool force = false);
- QStringList animAlphaValues() { return _animAlphaValues; }
- QStringList animVars() { return _animVarsList; }
- QStringList animStateMachines() { return _animStateMachines; }
+ QStringList animAlphaValues() const { return _animAlphaValues; }
+ QStringList animVars() const { return _animVarsList; }
+ QStringList animStateMachines() const { return _animStateMachines; }
+
+ QString positionText() const { return _positionText; }
+ QString rotationText() const { return _rotationText; }
+ QString velocityText() const { return _velocityText; }
public slots:
void forceUpdateStats() { updateStats(true); }
@@ -39,6 +46,9 @@ signals:
void animAlphaValuesChanged();
void animVarsChanged();
void animStateMachinesChanged();
+ void positionTextChanged();
+ void rotationTextChanged();
+ void velocityTextChanged();
private:
QStringList _animAlphaValues;
@@ -50,6 +60,10 @@ private:
std::map _animVarChangedTimers; // last time animVar value has changed.
QStringList _animStateMachines;
+
+ QString _positionText;
+ QString _rotationText;
+ QString _velocityText;
};
#endif // hifi_AnimStats_h
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp
index ba9a1f9fc9..5de8bb1a2a 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.cpp
+++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp
@@ -252,12 +252,6 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt
void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) {
qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID;
- Setting::Handle _settingSwitch{ "commerce", true };
- if (_settingSwitch.get()) {
- openInspectionCertificate();
- } else {
- openMarketplace();
- }
emit contextOverlayClicked(_currentEntityWithContextOverlay);
_contextOverlayJustClicked = true;
}
@@ -390,34 +384,6 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
}
}
-static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
-void ContextOverlayInterface::openInspectionCertificate() {
- // lets open the tablet to the inspection certificate QML
- if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
- setLastInspectedEntity(_currentEntityWithContextOverlay);
- auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
- tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
- _hmdScriptingInterface->openTablet();
- }
-}
-
-static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
-
-void ContextOverlayInterface::openMarketplace() {
- // lets open the tablet and go to the current item in
- // the marketplace (if the current entity has a
- // marketplaceID)
- if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
- auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
- // construct the url to the marketplace item
- QString url = MARKETPLACE_BASE_URL + _entityMarketplaceID;
- QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js";
- tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH);
- _hmdScriptingInterface->openTablet();
- _isInMarketplaceInspectionMode = true;
- }
-}
-
void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityItemID) {
_selectionScriptingInterface->addToSelectedItemsList("contextOverlayHighlightList", "entity", entityItemID);
}
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h
index 808c3a4ee3..48b14e1a91 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.h
+++ b/interface/src/ui/overlays/ContextOverlayInterface.h
@@ -94,8 +94,6 @@ private:
bool _isInMarketplaceInspectionMode { false };
- void openInspectionCertificate();
- void openMarketplace();
void enableEntityHighlight(const EntityItemID& entityItemID);
void disableEntityHighlight(const EntityItemID& entityItemID);
diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp
index 7f46cd614a..fb13b8e71c 100644
--- a/libraries/animation/src/AnimStateMachine.cpp
+++ b/libraries/animation/src/AnimStateMachine.cpp
@@ -88,6 +88,10 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
processOutputJoints(triggersOut);
context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha);
+ if (_duringInterp) {
+ // hack: add previoius state to debug alpha map, with parens around it's name.
+ context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip);
+ }
return _poses;
}
diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp
index 509462984a..21fe234f7b 100644
--- a/libraries/animation/src/AnimVariant.cpp
+++ b/libraries/animation/src/AnimVariant.cpp
@@ -140,14 +140,19 @@ std::map AnimVariantMap::toDebugMap() const {
result[pair.first] = QString::number(pair.second.getFloat(), 'f', 3);
break;
case AnimVariant::Type::Vec3: {
+ // To prevent filling up debug stats, don't show vec3 values
+ /*
glm::vec3 value = pair.second.getVec3();
result[pair.first] = QString("(%1, %2, %3)").
arg(QString::number(value.x, 'f', 3)).
arg(QString::number(value.y, 'f', 3)).
arg(QString::number(value.z, 'f', 3));
+ */
break;
}
case AnimVariant::Type::Quat: {
+ // To prevent filling up the anim stats, don't show quat values
+ /*
glm::quat value = pair.second.getQuat();
result[pair.first] = QString("(%1, %2, %3, %4)").
arg(QString::number(value.x, 'f', 3)).
@@ -155,10 +160,14 @@ std::map AnimVariantMap::toDebugMap() const {
arg(QString::number(value.z, 'f', 3)).
arg(QString::number(value.w, 'f', 3));
break;
+ */
}
case AnimVariant::Type::String:
+ // To prevent filling up anim stats, don't show string values
+ /*
result[pair.first] = pair.second.getString();
break;
+ */
default:
assert(("invalid AnimVariant::Type", false));
}
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 3a33eccc8a..d00bc29054 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -53,7 +53,6 @@
#include "AudioHelpers.h"
#if defined(Q_OS_ANDROID)
-#define VOICE_RECOGNITION "voicerecognition"
#include
#endif
@@ -101,6 +100,13 @@ QList getAvailableDevices(QAudio::Mode mode) {
// now called from a background thread, to keep blocking operations off the audio thread
void AudioClient::checkDevices() {
+ // Make sure we're not shutting down
+ Lock timerMutex(_checkDevicesMutex);
+ // If we HAVE shut down after we were queued, but prior to execution, early exit
+ if (nullptr == _checkDevicesTimer) {
+ return;
+ }
+
auto inputDevices = getAvailableDevices(QAudio::AudioInput);
auto outputDevices = getAvailableDevices(QAudio::AudioOutput);
@@ -210,6 +216,7 @@ AudioClient::AudioClient() :
_positionGetter(DEFAULT_POSITION_GETTER),
#if defined(Q_OS_ANDROID)
_checkInputTimer(this),
+ _isHeadsetPluggedIn(false),
#endif
_orientationGetter(DEFAULT_ORIENTATION_GETTER) {
// avoid putting a lock in the device callback
@@ -278,9 +285,6 @@ void AudioClient::customDeleter() {
_shouldRestartInputSetup = false;
#endif
stop();
- _checkDevicesTimer->stop();
- _checkPeakValuesTimer->stop();
-
deleteLater();
}
@@ -461,9 +465,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
#if defined (Q_OS_ANDROID)
if (mode == QAudio::AudioInput) {
+ Setting::Handle enableAEC(SETTING_AEC_KEY, false);
+ bool aecEnabled = enableAEC.get();
+ auto audioClient = DependencyManager::get();
+ bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false;
auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
for (auto inputDevice : inputDevices) {
- if (inputDevice.deviceName() == VOICE_RECOGNITION) {
+ if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) ||
+ ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) {
return inputDevice;
}
}
@@ -648,12 +657,26 @@ void AudioClient::start() {
}
void AudioClient::stop() {
-
qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down";
switchInputToAudioDevice(QAudioDeviceInfo(), true);
qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down";
switchOutputToAudioDevice(QAudioDeviceInfo(), true);
+
+ // Stop triggering the checks
+ QObject::disconnect(_checkPeakValuesTimer, &QTimer::timeout, nullptr, nullptr);
+ QObject::disconnect(_checkDevicesTimer, &QTimer::timeout, nullptr, nullptr);
+
+ // Destruction of the pointers will occur when the parent object (this) is destroyed)
+ {
+ Lock lock(_checkDevicesMutex);
+ _checkDevicesTimer = nullptr;
+ }
+ {
+ Lock lock(_checkPeakValuesMutex);
+ _checkPeakValuesTimer = nullptr;
+ }
+
#if defined(Q_OS_ANDROID)
_checkInputTimer.stop();
disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0);
@@ -1640,6 +1663,29 @@ void AudioClient::checkInputTimeout() {
#endif
}
+void AudioClient::setHeadsetPluggedIn(bool pluggedIn) {
+#if defined(Q_OS_ANDROID)
+ if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.isNull()) {
+ QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND");
+ // some samsung phones needs more time to shutdown the previous input device
+ if (brand.toString().contains("samsung", Qt::CaseInsensitive)) {
+ switchInputToAudioDevice(QAudioDeviceInfo(), true);
+ QThread::msleep(200);
+ }
+
+ Setting::Handle enableAEC(SETTING_AEC_KEY, false);
+ bool aecEnabled = enableAEC.get();
+
+ if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) {
+ switchAudioDevice(QAudio::AudioInput, VOICE_RECOGNITION);
+ } else if (!pluggedIn && aecEnabled && _inputDeviceInfo.deviceName() != VOICE_COMMUNICATION) {
+ switchAudioDevice(QAudio::AudioInput, VOICE_COMMUNICATION);
+ }
+ }
+ _isHeadsetPluggedIn = pluggedIn;
+#endif
+}
+
void AudioClient::outputNotify() {
int recentUnfulfilled = _audioOutputIODevice.getRecentUnfulfilledReads();
if (recentUnfulfilled > 0) {
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 4640d7c045..5e7f1fb8a0 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -64,6 +64,13 @@
#pragma warning( pop )
#endif
+#if defined (Q_OS_ANDROID)
+#define VOICE_RECOGNITION "voicerecognition"
+#define VOICE_COMMUNICATION "voicecommunication"
+
+#define SETTING_AEC_KEY "Android/aec"
+#endif
+
class QAudioInput;
class QAudioOutput;
class QIODevice;
@@ -169,6 +176,10 @@ public:
static QString getWinDeviceName(wchar_t* guid);
#endif
+#if defined(Q_OS_ANDROID)
+ bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; }
+#endif
+
public slots:
void start();
void stop();
@@ -217,6 +228,9 @@ public slots:
bool switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo = QAudioDeviceInfo());
bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName);
+ // Qt opensles plugin is not able to detect when the headset is plugged in
+ void setHeadsetPluggedIn(bool pluggedIn);
+
float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; }
void setInputVolume(float volume, bool emitSignal = true);
void setReverb(bool reverb);
@@ -278,6 +292,7 @@ private:
#ifdef Q_OS_ANDROID
QTimer _checkInputTimer;
long _inputReadsSinceLastCheck = 0l;
+ bool _isHeadsetPluggedIn;
#endif
class Gate {
@@ -432,7 +447,9 @@ private:
bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
#endif
+ Mutex _checkDevicesMutex;
QTimer* _checkDevicesTimer { nullptr };
+ Mutex _checkPeakValuesMutex;
QTimer* _checkPeakValuesTimer { nullptr };
bool _isRecording { false };
diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp
index 0b8921a117..a50567da7f 100644
--- a/libraries/audio-client/src/AudioPeakValues.cpp
+++ b/libraries/audio-client/src/AudioPeakValues.cpp
@@ -40,6 +40,12 @@ void release(IAudioClient* audioClient) {
}
void AudioClient::checkPeakValues() {
+ // Guard against running during shutdown
+ Lock timerMutex(_checkPeakValuesMutex);
+ if (nullptr == _checkPeakValuesTimer) {
+ return;
+ }
+
// prepare the windows environment
CoInitialize(NULL);
diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp
index 67f9952771..da284f19a3 100644
--- a/libraries/audio/src/Sound.cpp
+++ b/libraries/audio/src/Sound.cpp
@@ -43,8 +43,11 @@ void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPo
}
}
-SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) {
- QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
+SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) {
+ // During shutdown we can sometimes get an empty sound pointer back
+ if (_sound) {
+ QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
+ }
}
Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h
index 348600e4ae..a0544870d0 100644
--- a/libraries/audio/src/Sound.h
+++ b/libraries/audio/src/Sound.h
@@ -105,11 +105,11 @@ class SoundScriptingInterface : public QObject {
Q_PROPERTY(float duration READ getDuration)
public:
- SoundScriptingInterface(SharedSoundPointer sound);
- SharedSoundPointer getSound() { return _sound; }
+ SoundScriptingInterface(const SharedSoundPointer& sound);
+ const SharedSoundPointer& getSound() { return _sound; }
- bool isReady() const { return _sound->isReady(); }
- float getDuration() { return _sound->getDuration(); }
+ bool isReady() const { return _sound ? _sound->isReady() : false; }
+ float getDuration() { return _sound ? _sound->getDuration() : 0.0f; }
/**jsdoc
* Triggered when the sound has been downloaded and is ready to be played.
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 834754e228..acf5696d65 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -2829,8 +2829,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
value.extraInfo = object.property("extraInfo").toVariant().toMap();
}
+// these coefficients can be changed via JS for experimental tuning
+// use AvatatManager.setAvatarSortCoefficient("name", value) by a user with domain kick-rights
float AvatarData::_avatarSortCoefficientSize { 8.0f };
-float AvatarData::_avatarSortCoefficientCenter { 4.0f };
+float AvatarData::_avatarSortCoefficientCenter { 0.25f };
float AvatarData::_avatarSortCoefficientAge { 1.0f };
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index d205a915f8..01557e307e 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -66,6 +66,22 @@ void AvatarReplicas::removeReplicas(const QUuid& parentID) {
}
}
+std::vector AvatarReplicas::takeReplicas(const QUuid& parentID) {
+ std::vector replicas;
+
+ auto it = _replicasMap.find(parentID);
+
+ if (it != _replicasMap.end()) {
+ // take a copy of the replica shared pointers for this parent
+ replicas.swap(it->second);
+
+ // erase the replicas for this parent from our map
+ _replicasMap.erase(it);
+ }
+
+ return replicas;
+}
+
void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) {
if (_replicasMap.find(parentID) != _replicasMap.end()) {
auto &replicas = _replicasMap[parentID];
@@ -386,24 +402,31 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S
}
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
- QWriteLocker locker(&_hashLock);
+ std::vector removedAvatars;
- auto replicaIDs = _replicas.getReplicaIDs(sessionUUID);
- _replicas.removeReplicas(sessionUUID);
- for (auto id : replicaIDs) {
- auto removedReplica = _avatarHash.take(id);
- if (removedReplica) {
- handleRemovedAvatar(removedReplica, removalReason);
+ {
+ QWriteLocker locker(&_hashLock);
+
+ auto replicas = _replicas.takeReplicas(sessionUUID);
+
+ for (auto& replica : replicas) {
+ auto removedReplica = _avatarHash.take(replica->getID());
+ if (removedReplica) {
+ removedAvatars.push_back(removedReplica);
+ }
+ }
+
+ _pendingAvatars.remove(sessionUUID);
+ auto removedAvatar = _avatarHash.take(sessionUUID);
+
+ if (removedAvatar) {
+ removedAvatars.push_back(removedAvatar);
}
}
- _pendingAvatars.remove(sessionUUID);
- auto removedAvatar = _avatarHash.take(sessionUUID);
-
- if (removedAvatar) {
+ for (auto& removedAvatar: removedAvatars) {
handleRemovedAvatar(removedAvatar, removalReason);
}
-
}
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
@@ -421,11 +444,18 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol
}
void AvatarHashMap::clearOtherAvatars() {
- QWriteLocker locker(&_hashLock);
+ QList removedAvatars;
- for (auto& av : _avatarHash) {
- handleRemovedAvatar(av);
+ {
+ QWriteLocker locker(&_hashLock);
+
+ // grab a copy of the current avatars so we can call handleRemoveAvatar for them
+ removedAvatars = _avatarHash.values();
+
+ _avatarHash.clear();
}
- _avatarHash.clear();
+ for (auto& av : removedAvatars) {
+ handleRemovedAvatar(av);
+ }
}
diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h
index 70d7f8c04d..c2cb448e52 100644
--- a/libraries/avatars/src/AvatarHashMap.h
+++ b/libraries/avatars/src/AvatarHashMap.h
@@ -49,6 +49,7 @@ public:
void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer);
void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged);
void removeReplicas(const QUuid& parentID);
+ std::vector takeReplicas(const QUuid& parentID);
void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType,
@@ -179,7 +180,7 @@ protected:
bool& isNew);
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
-
+
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
AvatarHash _avatarHash;
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 8be5b172ce..3a01650a04 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -1322,6 +1322,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
emit DependencyManager::get()->
modelRemovedFromScene(entity->getEntityItemID(), NestableType::Entity, _model);
}
+ setKey(false);
+ _didLastVisualGeometryRequestSucceed = false;
return;
}
@@ -1347,6 +1349,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
if (_parsedModelURL != model->getURL()) {
withWriteLock([&] {
_texturesLoaded = false;
+ _jointMappingCompleted = false;
model->setURL(_parsedModelURL);
});
}
@@ -1457,7 +1460,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
mapJoints(entity, model->getJointNames());
//else the joint have been mapped before but we have a new animation to load
} else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) {
- _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL());
_jointMappingCompleted = false;
mapJoints(entity, model->getJointNames());
}
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h
index 45892fdd7f..75f35fae9c 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h
@@ -187,7 +187,7 @@ private:
const void* _collisionMeshKey { nullptr };
// used on client side
- bool _jointMappingCompleted{ false };
+ bool _jointMappingCompleted { false };
QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints
AnimationPointer _animation;
QUrl _parsedModelURL;
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index 18c4921836..1a263fba79 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -187,12 +187,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
particle.basePosition = baseTransform.getTranslation();
// Position, velocity, and acceleration
+ glm::vec3 emitDirection;
if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) {
// Emit along z-axis from position
-
- particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * Vectors::UNIT_Z);
- particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread;
-
+ emitDirection = Vectors::UNIT_Z;
} else {
// Emit around point or from ellipsoid
// - Distribute directions evenly around point
@@ -210,7 +208,6 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
azimuth = azimuthStart + (TWO_PI + azimuthFinish - azimuthStart) * randFloat();
}
- glm::vec3 emitDirection;
if (emitDimensions == Vectors::ZERO) {
// Point
emitDirection = glm::quat(glm::vec3(PI_OVER_TWO - elevation, 0.0f, azimuth)) * Vectors::UNIT_Z;
@@ -235,10 +232,10 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
));
particle.relativePosition += emitOrientation * emitPosition;
}
-
- particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection);
- particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread;
}
+ particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection);
+ particle.acceleration = emitAcceleration +
+ glm::vec3(randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f), randFloatInRange(-1.0f, 1.0f)) * accelerationSpread;
return particle;
}
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index d9924cb9fd..8f0fde5c9a 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -132,8 +132,8 @@ EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperti
EntityItemProperties scriptSideProperties = entitySideProperties;
scriptSideProperties.setLocalPosition(entitySideProperties.getPosition());
scriptSideProperties.setLocalRotation(entitySideProperties.getRotation());
- scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity());
- scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity());
+ scriptSideProperties.setLocalVelocity(entitySideProperties.getVelocity());
+ scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getAngularVelocity());
scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions());
bool success;
@@ -181,8 +181,6 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper
EntityItemProperties entitySideProperties = scriptSideProperties;
bool success;
- // TODO -- handle velocity and angularVelocity
-
if (scriptSideProperties.localPositionChanged()) {
entitySideProperties.setPosition(scriptSideProperties.getLocalPosition());
} else if (scriptSideProperties.positionChanged()) {
diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp
index 774559a628..f006692415 100644
--- a/libraries/entities/src/ModelEntityItem.cpp
+++ b/libraries/entities/src/ModelEntityItem.cpp
@@ -66,7 +66,9 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP
COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints);
- _animationProperties.getProperties(properties);
+ withReadLock([&] {
+ _animationProperties.getProperties(properties);
+ });
return properties;
}
@@ -123,15 +125,18 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
// grab a local copy of _animationProperties to avoid multiple locks
int bytesFromAnimation;
+ AnimationPropertyGroup animationProperties;
withReadLock([&] {
- AnimationPropertyGroup animationProperties = _animationProperties;
+ animationProperties = _animationProperties;
bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, animationPropertiesChanged);
- if (animationPropertiesChanged) {
- applyNewAnimationProperties(animationProperties);
- somethingChanged = true;
- }
});
+ if (animationPropertiesChanged) {
+ withWriteLock([&] {
+ applyNewAnimationProperties(animationProperties);
+ });
+ somethingChanged = true;
+ }
bytesRead += bytesFromAnimation;
dataAt += bytesFromAnimation;
@@ -305,69 +310,77 @@ void ModelEntityItem::setAnimationURL(const QString& url) {
void ModelEntityItem::setAnimationSettings(const QString& value) {
// NOTE: this method only called for old bitstream format
+ AnimationPropertyGroup animationProperties;
+ withReadLock([&] {
+ animationProperties = _animationProperties;
+ });
+
+ // the animations setting is a JSON string that may contain various animation settings.
+ // if it includes fps, currentFrame, or running, those values will be parsed out and
+ // will over ride the regular animation settings
+ QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
+ QJsonObject settingsAsJsonObject = settingsAsJson.object();
+ QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
+ if (settingsMap.contains("fps")) {
+ float fps = settingsMap["fps"].toFloat();
+ animationProperties.setFPS(fps);
+ }
+
+ // old settings used frameIndex
+ if (settingsMap.contains("frameIndex")) {
+ float currentFrame = settingsMap["frameIndex"].toFloat();
+ animationProperties.setCurrentFrame(currentFrame);
+ }
+
+ if (settingsMap.contains("running")) {
+ bool running = settingsMap["running"].toBool();
+ if (running != animationProperties.getRunning()) {
+ animationProperties.setRunning(running);
+ }
+ }
+
+ if (settingsMap.contains("firstFrame")) {
+ float firstFrame = settingsMap["firstFrame"].toFloat();
+ animationProperties.setFirstFrame(firstFrame);
+ }
+
+ if (settingsMap.contains("lastFrame")) {
+ float lastFrame = settingsMap["lastFrame"].toFloat();
+ animationProperties.setLastFrame(lastFrame);
+ }
+
+ if (settingsMap.contains("loop")) {
+ bool loop = settingsMap["loop"].toBool();
+ animationProperties.setLoop(loop);
+ }
+
+ if (settingsMap.contains("hold")) {
+ bool hold = settingsMap["hold"].toBool();
+ animationProperties.setHold(hold);
+ }
+
+ if (settingsMap.contains("allowTranslation")) {
+ bool allowTranslation = settingsMap["allowTranslation"].toBool();
+ animationProperties.setAllowTranslation(allowTranslation);
+ }
+
withWriteLock([&] {
- auto animationProperties = _animationProperties;
-
- // the animations setting is a JSON string that may contain various animation settings.
- // if it includes fps, currentFrame, or running, those values will be parsed out and
- // will over ride the regular animation settings
- QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
- QJsonObject settingsAsJsonObject = settingsAsJson.object();
- QVariantMap settingsMap = settingsAsJsonObject.toVariantMap();
- if (settingsMap.contains("fps")) {
- float fps = settingsMap["fps"].toFloat();
- animationProperties.setFPS(fps);
- }
-
- // old settings used frameIndex
- if (settingsMap.contains("frameIndex")) {
- float currentFrame = settingsMap["frameIndex"].toFloat();
- animationProperties.setCurrentFrame(currentFrame);
- }
-
- if (settingsMap.contains("running")) {
- bool running = settingsMap["running"].toBool();
- if (running != animationProperties.getRunning()) {
- animationProperties.setRunning(running);
- }
- }
-
- if (settingsMap.contains("firstFrame")) {
- float firstFrame = settingsMap["firstFrame"].toFloat();
- animationProperties.setFirstFrame(firstFrame);
- }
-
- if (settingsMap.contains("lastFrame")) {
- float lastFrame = settingsMap["lastFrame"].toFloat();
- animationProperties.setLastFrame(lastFrame);
- }
-
- if (settingsMap.contains("loop")) {
- bool loop = settingsMap["loop"].toBool();
- animationProperties.setLoop(loop);
- }
-
- if (settingsMap.contains("hold")) {
- bool hold = settingsMap["hold"].toBool();
- animationProperties.setHold(hold);
- }
-
- if (settingsMap.contains("allowTranslation")) {
- bool allowTranslation = settingsMap["allowTranslation"].toBool();
- animationProperties.setAllowTranslation(allowTranslation);
- }
applyNewAnimationProperties(animationProperties);
});
}
void ModelEntityItem::setAnimationIsPlaying(bool value) {
_flags |= Simulation::DIRTY_UPDATEABLE;
- _animationProperties.setRunning(value);
+ withWriteLock([&] {
+ _animationProperties.setRunning(value);
+ });
}
void ModelEntityItem::setAnimationFPS(float value) {
_flags |= Simulation::DIRTY_UPDATEABLE;
- _animationProperties.setFPS(value);
+ withWriteLock([&] {
+ _animationProperties.setFPS(value);
+ });
}
// virtual
@@ -557,11 +570,9 @@ void ModelEntityItem::setColor(const xColor& value) {
// Animation related items...
AnimationPropertyGroup ModelEntityItem::getAnimationProperties() const {
- AnimationPropertyGroup result;
- withReadLock([&] {
- result = _animationProperties;
+ return resultWithReadLock([&] {
+ return _animationProperties;
});
- return result;
}
bool ModelEntityItem::hasAnimation() const {
@@ -582,6 +593,18 @@ void ModelEntityItem::setAnimationCurrentFrame(float value) {
});
}
+void ModelEntityItem::setAnimationAllowTranslation(bool value) {
+ withWriteLock([&] {
+ _animationProperties.setAllowTranslation(value);
+ });
+}
+
+bool ModelEntityItem::getAnimationAllowTranslation() const {
+ return resultWithReadLock([&] {
+ return _animationProperties.getAllowTranslation();
+ });
+}
+
void ModelEntityItem::setAnimationLoop(bool loop) {
withWriteLock([&] {
_animationProperties.setLoop(loop);
diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
index ad6cdf4040..ef758b9dde 100644
--- a/libraries/entities/src/ModelEntityItem.h
+++ b/libraries/entities/src/ModelEntityItem.h
@@ -88,8 +88,8 @@ public:
void setAnimationIsPlaying(bool value);
void setAnimationFPS(float value);
- void setAnimationAllowTranslation(bool value) { _animationProperties.setAllowTranslation(value); };
- bool getAnimationAllowTranslation() const { return _animationProperties.getAllowTranslation(); };
+ void setAnimationAllowTranslation(bool value);
+ bool getAnimationAllowTranslation() const;
void setAnimationLoop(bool loop);
bool getAnimationLoop() const;
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp
index 8a67ff9619..46f91fdc15 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp
@@ -57,7 +57,7 @@ void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& pro
const auto resourceBufferUniforms = ::gl::Uniform::loadByName(glprogram, program.getResourceBuffers().getNames());
for (const auto& resourceBuffer : resourceBufferUniforms) {
const auto& targetBinding = expectedResourceBuffers.at(resourceBuffer.name);
- glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding);
+ glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT);
}
}
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index 5b3196a2bf..d99c0020da 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -538,7 +538,6 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
QNetworkReply* requestReply = networkAccessManager.post(request, postData);
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished);
- connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
}
void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
@@ -633,12 +632,6 @@ void AccountManager::requestAccessTokenFinished() {
}
}
-void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) {
- // TODO: error handling
- qCDebug(networking) << "AccountManager: failed to fetch access token - " << error;
- emit loginFailed();
-}
-
void AccountManager::refreshAccessTokenFinished() {
QNetworkReply* requestReply = reinterpret_cast(sender());
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index b122115dd0..f3b81cf1c9 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -106,7 +106,6 @@ public slots:
void requestAccessTokenFinished();
void refreshAccessTokenFinished();
void requestProfileFinished();
- void requestAccessTokenError(QNetworkReply::NetworkError error);
void refreshAccessTokenError(QNetworkReply::NetworkError error);
void requestProfileError(QNetworkReply::NetworkError error);
void logout();
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index c7cdf8f4ea..17041a5fd7 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -140,8 +140,7 @@ public:
*
* @typedef {number} location.LookupTrigger
*/
- enum LookupTrigger
- {
+ enum LookupTrigger {
UserInput,
Back,
Forward,
@@ -207,9 +206,8 @@ public slots:
// functions and signals that should be exposed are moved to a scripting interface class.
//
// we currently expect this to be called from NodeList once handleLookupString has been called with a path
- bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) {
- return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString);
- }
+ bool goToViewpointForPath(const QString& viewpointString, const QString& pathString)
+ { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); }
/**jsdoc
* Go back to the previous location in your navigation history, if there is one.
@@ -231,8 +229,7 @@ public slots:
* location history is correctly maintained.
*/
void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) {
- handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger);
- }
+ handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
/**jsdoc
* Go to the default "welcome" metaverse address.
@@ -364,8 +361,7 @@ signals:
* location.locationChangeRequired.connect(onLocationChangeRequired);
*/
void locationChangeRequired(const glm::vec3& newPosition,
- bool hasOrientationChange,
- const glm::quat& newOrientation,
+ bool hasOrientationChange, const glm::quat& newOrientation,
bool shouldFaceLocation);
/**jsdoc
@@ -448,11 +444,8 @@ private:
bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged);
void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false);
- bool handleViewpoint(const QString& viewpointString,
- bool shouldFace,
- LookupTrigger trigger,
- bool definitelyPathOnly = false,
- const QString& pathString = QString());
+ bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger,
+ bool definitelyPathOnly = false, const QString& pathString = QString());
bool handleUsername(const QString& lookupString);
bool handleDomainID(const QString& host);
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index f34a93de96..df34a1fb59 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -15,6 +15,10 @@
#include
+#include
+
+#include
+
#include
#include
@@ -134,6 +138,18 @@ void DomainHandler::hardReset() {
_pendingPath.clear();
}
+bool DomainHandler::getInterstitialModeEnabled() const {
+ return _interstitialModeSettingLock.resultWithReadLock([&] {
+ return _enableInterstitialMode.get();
+ });
+}
+
+void DomainHandler::setInterstitialModeEnabled(bool enableInterstitialMode) {
+ _interstitialModeSettingLock.withWriteLock([&] {
+ _enableInterstitialMode.set(enableInterstitialMode);
+ });
+}
+
void DomainHandler::setErrorDomainURL(const QUrl& url) {
_errorDomainURL = url;
return;
@@ -340,11 +356,15 @@ void DomainHandler::loadedErrorDomain(std::map namedPaths) {
DependencyManager::get()->goToViewpointForPath(viewpoint, QString());
}
-void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) {
- _errorDomainURL = errorUrl;
+void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) {
_lastDomainConnectionError = reasonCode;
- _isInErrorState = true;
- emit redirectToErrorDomainURL(_errorDomainURL);
+ if (getInterstitialModeEnabled()) {
+ _errorDomainURL = errorUrl;
+ _isInErrorState = true;
+ emit redirectToErrorDomainURL(_errorDomainURL);
+ } else {
+ emit domainConnectionRefused(reasonMessage, reasonCode, extraInfo);
+ }
}
void DomainHandler::requestDomainSettings() {
@@ -485,13 +505,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer
#include
+#include
+#include
+
#include "HifiSockAddr.h"
#include "NetworkPeer.h"
#include "NLPacket.h"
@@ -83,6 +86,8 @@ public:
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; }
+ bool getInterstitialModeEnabled() const;
+ void setInterstitialModeEnabled(bool enableInterstitialMode);
void connectedToServerless(std::map namedPaths);
@@ -171,7 +176,7 @@ public slots:
void processDomainServerConnectionDeniedPacket(QSharedPointer message);
// sets domain handler in error state.
- void setRedirectErrorState(QUrl errorUrl, int reasonCode);
+ void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reason = -1, const QString& extraInfo = "");
bool isInErrorState() { return _isInErrorState; }
@@ -224,6 +229,8 @@ private:
QJsonObject _settingsObject;
QString _pendingPath;
QTimer _settingsTimer;
+ mutable ReadWriteLockable _interstitialModeSettingLock;
+ Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false };
QSet _domainConnectionRefusals;
bool _hasCheckedForAccessToken { false };
diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh
index fe77c38fd5..b26337676f 100644
--- a/libraries/render-utils/src/Highlight.slh
+++ b/libraries/render-utils/src/Highlight.slh
@@ -15,7 +15,7 @@
<@include Highlight_shared.slh@>
-layout(binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer {
+layout(std140, binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer {
HighlightParameters params;
};
diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp
index 11326b1120..bcac31dd5a 100644
--- a/libraries/render-utils/src/HighlightEffect.cpp
+++ b/libraries/render-utils/src/HighlightEffect.cpp
@@ -37,6 +37,8 @@ namespace gr {
#define OUTLINE_STENCIL_MASK 1
+extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state);
+
HighlightRessources::HighlightRessources() {
}
@@ -180,6 +182,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c
auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder);
auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
+ auto maskSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned());
// Setup camera, projection and viewport for all items
batch.setViewportTransform(args->_viewport);
@@ -187,14 +190,17 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c
batch.setProjectionJitter(jitter.x, jitter.y);
batch.setViewTransform(viewMat);
- std::vector skinnedShapeKeys{};
+ std::vector skinnedShapeKeys;
+ std::vector skinnedDQShapeKeys;
// Iterate through all inShapes and render the unskinned
args->_shapePipeline = maskPipeline;
batch.setPipeline(maskPipeline->pipeline);
for (const auto& items : inShapes) {
itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end());
- if (items.first.isSkinned()) {
+ if (items.first.isSkinned() && items.first.isDualQuatSkinned()) {
+ skinnedDQShapeKeys.push_back(items.first);
+ } else if (items.first.isSkinned()) {
skinnedShapeKeys.push_back(items.first);
} else {
renderItems(renderContext, items.second);
@@ -202,10 +208,21 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c
}
// Reiterate to render the skinned
- args->_shapePipeline = maskSkinnedPipeline;
- batch.setPipeline(maskSkinnedPipeline->pipeline);
- for (const auto& key : skinnedShapeKeys) {
- renderItems(renderContext, inShapes.at(key));
+ if (skinnedShapeKeys.size() > 0) {
+ args->_shapePipeline = maskSkinnedPipeline;
+ batch.setPipeline(maskSkinnedPipeline->pipeline);
+ for (const auto& key : skinnedShapeKeys) {
+ renderItems(renderContext, inShapes.at(key));
+ }
+ }
+
+ // Reiterate to render the DQ skinned
+ if (skinnedDQShapeKeys.size() > 0) {
+ args->_shapePipeline = maskSkinnedDQPipeline;
+ batch.setPipeline(maskSkinnedDQPipeline->pipeline);
+ for (const auto& key : skinnedDQShapeKeys) {
+ renderItems(renderContext, inShapes.at(key));
+ }
}
args->_shapePipeline = nullptr;
@@ -488,7 +505,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setColorWriteMask(false, false, false, false);
- initMaskPipelines(*shapePlumber, state);
+ initZPassPipelines(*shapePlumber, state);
}
auto sharedParameters = std::make_shared();
@@ -548,16 +565,4 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const
const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput);
const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying();
return task.addJob("TransparentSelection", selectItemInput);
-}
-
-void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) {
- gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(shader::render_utils::program::model_shadow);
- shapePlumber.addPipeline(
- ShapeKey::Filter::Builder().withoutSkinned(),
- modelProgram, state);
-
- gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(shader::render_utils::program::skin_model_shadow);
- shapePlumber.addPipeline(
- ShapeKey::Filter::Builder().withSkinned(),
- skinProgram, state);
-}
+}
\ No newline at end of file
diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h
index 64a97a549e..32668c1ab6 100644
--- a/libraries/render-utils/src/HighlightEffect.h
+++ b/libraries/render-utils/src/HighlightEffect.h
@@ -208,8 +208,6 @@ public:
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
private:
-
- static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state);
static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items);
};
diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv
index 5130d5e7ff..2ecebdea51 100644
--- a/libraries/render-utils/src/Highlight_aabox.slv
+++ b/libraries/render-utils/src/Highlight_aabox.slv
@@ -45,7 +45,7 @@ struct HighlightParameters {
vec2 outlineWidth;
};
-layout(binding=0) uniform parametersBuffer {
+layout(std140, binding=0) uniform parametersBuffer {
HighlightParameters _parameters;
};
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index b9ed43c339..ab6507b29c 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -1553,14 +1553,13 @@ void Model::setBlendedVertices(int blendNumber, const QVector& vertic
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
auto meshNormalsAndTangents = _normalsAndTangents.find(i);
- if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end()) {
+ const auto& buffer = _blendedVertexBuffers.find(i);
+ if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end() || buffer == _blendedVertexBuffers.end()) {
continue;
}
const auto vertexCount = mesh.vertices.size();
const auto verticesSize = vertexCount * sizeof(glm::vec3);
- const auto& buffer = _blendedVertexBuffers.find(i);
- assert(buffer != _blendedVertexBuffers.end());
buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType));
buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3));
buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType));
diff --git a/libraries/render-utils/src/parabola_translucent.slf b/libraries/render-utils/src/parabola_translucent.slf
new file mode 100644
index 0000000000..62b5b15193
--- /dev/null
+++ b/libraries/render-utils/src/parabola_translucent.slf
@@ -0,0 +1,18 @@
+<@include gpu/Config.slh@>
+<$VERSION_HEADER$>
+// Generated on <$_SCRIBE_DATE$>
+//
+// Created by Sam Gondelman on 9/10/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
+//
+
+<@include DeferredBufferWrite.slh@>
+
+layout(location=0) in vec4 _color;
+
+void main(void) {
+ packDeferredFragmentTranslucent(vec3(1.0, 0.0, 0.0), _color.a, _color.rgb, DEFAULT_FRESNEL, DEFAULT_ROUGHNESS);
+}
diff --git a/libraries/render-utils/src/render-utils/parabola_translucent.slp b/libraries/render-utils/src/render-utils/parabola_translucent.slp
new file mode 100644
index 0000000000..ab3f1d4126
--- /dev/null
+++ b/libraries/render-utils/src/render-utils/parabola_translucent.slp
@@ -0,0 +1 @@
+VERTEX parabola
diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp
index 2e0850255a..e5920b785e 100644
--- a/libraries/shared/src/SettingManager.cpp
+++ b/libraries/shared/src/SettingManager.cpp
@@ -52,7 +52,7 @@ namespace Setting {
if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) {
loadedValue = _pendingChanges[key];
} else {
- loadedValue = value(key);
+ loadedValue = _qSettings.value(key);
}
if (loadedValue.isValid()) {
handle->setVariant(loadedValue);
@@ -92,32 +92,115 @@ namespace Setting {
}
void Manager::saveAll() {
- bool forceSync = false;
withWriteLock([&] {
+ bool forceSync = false;
for (auto key : _pendingChanges.keys()) {
auto newValue = _pendingChanges[key];
- auto savedValue = value(key, UNSET_VALUE);
+ auto savedValue = _qSettings.value(key, UNSET_VALUE);
if (newValue == savedValue) {
continue;
}
+ forceSync = true;
if (newValue == UNSET_VALUE || !newValue.isValid()) {
- forceSync = true;
- remove(key);
+ _qSettings.remove(key);
} else {
- forceSync = true;
- setValue(key, newValue);
+ _qSettings.setValue(key, newValue);
}
}
_pendingChanges.clear();
- });
- if (forceSync) {
- sync();
- }
+ if (forceSync) {
+ _qSettings.sync();
+ }
+ });
// Restart timer
if (_saveTimer) {
_saveTimer->start();
}
}
+
+ QString Manager::fileName() const {
+ return resultWithReadLock([&] {
+ return _qSettings.fileName();
+ });
+ }
+
+ void Manager::remove(const QString &key) {
+ withWriteLock([&] {
+ _qSettings.remove(key);
+ });
+ }
+
+ QStringList Manager::childGroups() const {
+ return resultWithReadLock([&] {
+ return _qSettings.childGroups();
+ });
+ }
+
+ QStringList Manager::childKeys() const {
+ return resultWithReadLock([&] {
+ return _qSettings.childKeys();
+ });
+ }
+
+ QStringList Manager::allKeys() const {
+ return resultWithReadLock([&] {
+ return _qSettings.allKeys();
+ });
+ }
+
+ bool Manager::contains(const QString &key) const {
+ return resultWithReadLock([&] {
+ return _qSettings.contains(key);
+ });
+ }
+
+ int Manager::beginReadArray(const QString &prefix) {
+ return resultWithReadLock([&] {
+ return _qSettings.beginReadArray(prefix);
+ });
+ }
+
+ void Manager::beginGroup(const QString &prefix) {
+ withWriteLock([&] {
+ _qSettings.beginGroup(prefix);
+ });
+ }
+
+ void Manager::beginWriteArray(const QString &prefix, int size) {
+ withWriteLock([&] {
+ _qSettings.beginWriteArray(prefix, size);
+ });
+ }
+
+ void Manager::endArray() {
+ withWriteLock([&] {
+ _qSettings.endArray();
+ });
+ }
+
+ void Manager::endGroup() {
+ withWriteLock([&] {
+ _qSettings.endGroup();
+ });
+ }
+
+ void Manager::setArrayIndex(int i) {
+ withWriteLock([&] {
+ _qSettings.setArrayIndex(i);
+ });
+ }
+
+ void Manager::setValue(const QString &key, const QVariant &value) {
+ withWriteLock([&] {
+ _qSettings.setValue(key, value);
+ });
+ }
+
+ QVariant Manager::value(const QString &key, const QVariant &defaultValue) const {
+ return resultWithReadLock([&] {
+ return _qSettings.value(key, defaultValue);
+ });
+ }
}
diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h
index 6696a1ecf4..49f3bece4b 100644
--- a/libraries/shared/src/SettingManager.h
+++ b/libraries/shared/src/SettingManager.h
@@ -23,12 +23,28 @@
namespace Setting {
class Interface;
- class Manager : public QSettings, public ReadWriteLockable, public Dependency {
+ class Manager : public QObject, public ReadWriteLockable, public Dependency {
Q_OBJECT
public:
void customDeleter() override;
+ // thread-safe proxies into QSettings
+ QString fileName() const;
+ void remove(const QString &key);
+ QStringList childGroups() const;
+ QStringList childKeys() const;
+ QStringList allKeys() const;
+ bool contains(const QString &key) const;
+ int beginReadArray(const QString &prefix);
+ void beginGroup(const QString &prefix);
+ void beginWriteArray(const QString &prefix, int size = -1);
+ void endArray();
+ void endGroup();
+ void setArrayIndex(int i);
+ void setValue(const QString &key, const QVariant &value);
+ QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
+
protected:
~Manager();
void registerHandle(Interface* handle);
@@ -52,6 +68,9 @@ namespace Setting {
friend class Interface;
friend void cleanupSettingsSaveThread();
friend void setupSettingsSaveThread();
+
+
+ QSettings _qSettings;
};
}
diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp
index bb22a1e753..012e7aa1f5 100644
--- a/libraries/shared/src/SharedUtil.cpp
+++ b/libraries/shared/src/SharedUtil.cpp
@@ -19,8 +19,8 @@
#include
#include
#include
-#include
#include
+#include
#include
@@ -127,82 +127,10 @@ void usecTimestampNowForceClockSkew(qint64 clockSkew) {
::usecTimestampNowAdjust = clockSkew;
}
-static std::atomic TIME_REFERENCE { 0 }; // in usec
-static std::once_flag usecTimestampNowIsInitialized;
-static QElapsedTimer timestampTimer;
-
quint64 usecTimestampNow(bool wantDebug) {
- std::call_once(usecTimestampNowIsInitialized, [&] {
- TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec
- timestampTimer.start();
- });
-
- quint64 now;
- quint64 nsecsElapsed = timestampTimer.nsecsElapsed();
- quint64 usecsElapsed = nsecsElapsed / NSECS_PER_USEC; // nsec to usec
-
- // QElapsedTimer may not advance if the CPU has gone to sleep. In which case it
- // will begin to deviate from real time. We detect that here, and reset if necessary
- quint64 msecsCurrentTime = QDateTime::currentMSecsSinceEpoch();
- quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / USECS_PER_MSEC; // usecs to msecs
- int possibleSkew = msecsEstimate - msecsCurrentTime;
- const int TOLERANCE = 10 * MSECS_PER_SECOND; // up to 10 seconds of skew is tolerated
- if (abs(possibleSkew) > TOLERANCE) {
- // reset our TIME_REFERENCE and timer
- TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec
- timestampTimer.restart();
- now = TIME_REFERENCE + ::usecTimestampNowAdjust;
-
- if (wantDebug) {
- qCDebug(shared) << "usecTimestampNow() - resetting QElapsedTimer. ";
- qCDebug(shared) << " msecsCurrentTime:" << msecsCurrentTime;
- qCDebug(shared) << " msecsEstimate:" << msecsEstimate;
- qCDebug(shared) << " possibleSkew:" << possibleSkew;
- qCDebug(shared) << " TOLERANCE:" << TOLERANCE;
-
- qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed;
- qCDebug(shared) << " usecsElapsed:" << usecsElapsed;
-
- QDateTime currentLocalTime = QDateTime::currentDateTime();
-
- quint64 msecsNow = now / 1000; // usecs to msecs
- QDateTime nowAsString;
- nowAsString.setMSecsSinceEpoch(msecsNow);
-
- qCDebug(shared) << " now:" << now;
- qCDebug(shared) << " msecsNow:" << msecsNow;
-
- qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
- qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
- }
- } else {
- now = TIME_REFERENCE + usecsElapsed + ::usecTimestampNowAdjust;
- }
-
- if (wantDebug) {
- QDateTime currentLocalTime = QDateTime::currentDateTime();
-
- quint64 msecsNow = now / 1000; // usecs to msecs
- QDateTime nowAsString;
- nowAsString.setMSecsSinceEpoch(msecsNow);
-
- quint64 msecsTimeReference = TIME_REFERENCE / 1000; // usecs to msecs
- QDateTime timeReferenceAsString;
- timeReferenceAsString.setMSecsSinceEpoch(msecsTimeReference);
-
- qCDebug(shared) << "usecTimestampNow() - details... ";
- qCDebug(shared) << " TIME_REFERENCE:" << TIME_REFERENCE;
- qCDebug(shared) << " timeReferenceAsString:" << timeReferenceAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
- qCDebug(shared) << " usecTimestampNowAdjust:" << usecTimestampNowAdjust;
- qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed;
- qCDebug(shared) << " usecsElapsed:" << usecsElapsed;
- qCDebug(shared) << " now:" << now;
- qCDebug(shared) << " msecsNow:" << msecsNow;
- qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
- qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
- }
-
- return now;
+ using namespace std::chrono;
+ static const auto unixEpoch = system_clock::from_time_t(0);
+ return duration_cast(system_clock::now() - unixEpoch).count() + usecTimestampNowAdjust;
}
float secTimestampNow() {
diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js
index 6d2986768a..dab377911b 100644
--- a/scripts/modules/appUi.js
+++ b/scripts/modules/appUi.js
@@ -1,5 +1,5 @@
"use strict";
-/*global Tablet, Script*/
+/* global Tablet, Script */
//
// libraries/appUi.js
//
@@ -11,6 +11,7 @@
//
function AppUi(properties) {
+ var request = Script.require('request').request;
/* Example development order:
1. var AppUi = Script.require('appUi');
2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3).
@@ -31,37 +32,63 @@ function AppUi(properties) {
var that = this;
function defaultButton(name, suffix) {
var base = that[name] || (that.buttonPrefix + suffix);
- that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge
+ that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); // poor man's merge
}
// Defaults:
that.tabletName = "com.highfidelity.interface.tablet.system";
that.inject = "";
that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below.
+ that.additionalAppScreens = [];
that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen.
- return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix.
+ // Actual url may have prefix or suffix.
+ return (type === that.currentVisibleScreenType) &&
+ that.currentVisibleUrl &&
+ ((that.home.indexOf(that.currentVisibleUrl) > -1) ||
+ (that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1));
};
- that.setCurrentData = function setCurrentData(url) {
- that.currentUrl = url;
- that.type = /.qml$/.test(url) ? 'QML' : 'Web';
- }
- that.open = function open(optionalUrl) { // How to open the app.
+ that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) {
+ that.currentVisibleScreenType = type;
+ that.currentVisibleUrl = url;
+ };
+ that.open = function open(optionalUrl, optionalInject) { // How to open the app.
var url = optionalUrl || that.home;
- that.setCurrentData(url);
- if (that.isQML()) {
+ var inject = optionalInject || that.inject;
+
+ if (that.isQMLUrl(url)) {
that.tablet.loadQMLSource(url);
} else {
- that.tablet.gotoWebScreen(url, that.inject);
+ that.tablet.gotoWebScreen(url, inject);
+ }
+ };
+ // Opens some app on top of the current app (on desktop, opens new window)
+ that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) {
+ var inject = optionalInject || "";
+ if (that.isQMLUrl(url)) {
+ that.tablet.loadQMLOnTop(url);
+ } else {
+ that.tablet.loadWebScreenOnTop(url, inject);
}
};
that.close = function close() { // How to close the app.
- that.currentUrl = "";
+ that.currentVisibleUrl = "";
// for toolbar-mode: go back to home screen, this will close the window.
that.tablet.gotoHomeScreen();
};
that.buttonActive = function buttonActive(isActive) { // How to make the button active (white).
that.button.editProperties({isActive: isActive});
};
+ that.isQMLUrl = function isQMLUrl(url) {
+ var type = /.qml$/.test(url) ? 'QML' : 'Web';
+ return type === 'QML';
+ };
+ that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() {
+ return that.currentVisibleScreenType === 'QML';
+ };
+
+ //
+ // START Notification Handling Defaults
+ //
that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button.
// Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true.
that.button.editProperties({
@@ -69,16 +96,124 @@ function AppUi(properties) {
activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
});
};
- that.isQML = function isQML() { // We set type property in onClick.
- return that.type === 'QML';
+ that.notificationPollTimeout = false;
+ that.notificationPollTimeoutMs = 60000;
+ that.notificationPollEndpoint = false;
+ that.notificationPollStopPaginatingConditionMet = false;
+ that.notificationDataProcessPage = function (data) {
+ return data;
};
- that.eventSignal = function eventSignal() { // What signal to hook onMessage to.
- return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived;
+ that.notificationPollCallback = that.ignore;
+ that.notificationPollCaresAboutSince = false;
+ that.notificationInitialCallbackMade = false;
+ that.notificationDisplayBanner = function (message) {
+ Window.displayAnnouncement(message);
+ };
+ //
+ // END Notification Handling Defaults
+ //
+
+ // Handlers
+ that.onScreenChanged = function onScreenChanged(type, url) {
+ // Set isOpen, wireEventBridge, set buttonActive as appropriate,
+ // and finally call onOpened() or onClosed() IFF defined.
+ that.setCurrentVisibleScreenMetadata(type, url);
+ if (that.checkIsOpen(type, url)) {
+ that.wireEventBridge(true);
+ if (!that.isOpen) {
+ that.buttonActive(true);
+ if (that.onOpened) {
+ that.onOpened();
+ }
+ that.isOpen = true;
+ }
+ } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden?
+ that.wireEventBridge(false);
+ if (that.isOpen) {
+ that.buttonActive(false);
+ if (that.onClosed) {
+ that.onClosed();
+ }
+ that.isOpen = false;
+ }
+ }
+ console.debug(that.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type +
+ "\nNew screen URL: " + url + "\nCurrent app open status: " + that.isOpen + "\n");
};
// Overwrite with the given properties:
Object.keys(properties).forEach(function (key) { that[key] = properties[key]; });
+ //
+ // START Notification Handling
+ //
+ var METAVERSE_BASE = Account.metaverseServerURL;
+ var currentDataPageToRetrieve = 1;
+ var concatenatedServerResponse = new Array();
+ that.notificationPoll = function () {
+ if (!that.notificationPollEndpoint) {
+ return;
+ }
+
+ // User is "appearing offline"
+ if (GlobalServices.findableBy === "none") {
+ that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+ return;
+ }
+
+ var url = METAVERSE_BASE + that.notificationPollEndpoint;
+
+ if (that.notificationPollCaresAboutSince) {
+ url = url + "&since=" + (new Date().getTime());
+ }
+
+ console.debug(that.buttonName, 'polling for notifications at endpoint', url);
+
+ function requestCallback(error, response) {
+ if (error || (response.status !== 'success')) {
+ print("Error: unable to get", url, error || response.status);
+ that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+ return;
+ }
+
+ if (!that.notificationPollStopPaginatingConditionMet || that.notificationPollStopPaginatingConditionMet(response)) {
+ that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+
+ var notificationData;
+ if (concatenatedServerResponse.length) {
+ notificationData = concatenatedServerResponse;
+ } else {
+ notificationData = that.notificationDataProcessPage(response);
+ }
+ console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData));
+ that.notificationPollCallback(notificationData);
+ that.notificationInitialCallbackMade = true;
+ currentDataPageToRetrieve = 1;
+ concatenatedServerResponse = new Array();
+ } else {
+ concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response));
+ currentDataPageToRetrieve++;
+ request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback);
+ }
+ }
+
+ request({ uri: url }, requestCallback);
+ };
+
+ // This won't do anything if there isn't a notification endpoint set
+ that.notificationPoll();
+
+ function availabilityChanged() {
+ if (that.notificationPollTimeout) {
+ Script.clearTimeout(that.notificationPollTimeout);
+ that.notificationPollTimeout = false;
+ }
+ that.notificationPoll();
+ }
+ //
+ // END Notification Handling
+ //
+
// Properties:
that.tablet = Tablet.getTablet(that.tabletName);
// Must be after we gather properties.
@@ -100,65 +235,58 @@ function AppUi(properties) {
}
that.button = that.tablet.addButton(buttonOptions);
that.ignore = function ignore() { };
-
- // Handlers
- that.onScreenChanged = function onScreenChanged(type, url) {
- // Set isOpen, wireEventBridge, set buttonActive as appropriate,
- // and finally call onOpened() or onClosed() IFF defined.
- console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen);
- if (that.checkIsOpen(type, url)) {
- if (!that.isOpen) {
- that.wireEventBridge(true);
- that.buttonActive(true);
- if (that.onOpened) {
- that.onOpened();
- }
- that.isOpen = true;
- }
-
- } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden?
- if (that.isOpen) {
- that.wireEventBridge(false);
- that.buttonActive(false);
- if (that.onClosed) {
- that.onClosed();
- }
- that.isOpen = false;
- }
- }
- };
- that.hasEventBridge = false;
+ that.hasOutboundEventBridge = false;
+ that.hasInboundQmlEventBridge = false;
+ that.hasInboundHtmlEventBridge = false;
// HTML event bridge uses strings, not objects. Here we abstract over that.
// (Although injected javascript still has to use JSON.stringify/JSON.parse.)
- that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); };
- that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); };
+ that.sendToHtml = function (messageObject) {
+ that.tablet.emitScriptEvent(JSON.stringify(messageObject));
+ };
+ that.fromHtml = function (messageString) {
+ var parsedMessage = JSON.parse(messageString);
+ parsedMessage.messageSrc = "HTML";
+ that.onMessage(parsedMessage);
+ };
that.sendMessage = that.ignore;
that.wireEventBridge = function wireEventBridge(on) {
// Uniquivocally sets that.sendMessage(messageObject) to do the right thing.
- // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined.
- var handler, isQml = that.isQML();
+ // Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined.
+ var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen();
// Outbound (always, regardless of whether there is an inbound handler).
if (on) {
- that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml;
+ that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml;
+ that.hasOutboundEventBridge = true;
} else {
that.sendMessage = that.ignore;
+ that.hasOutboundEventBridge = false;
}
- if (!that.onMessage) { return; }
+ if (!that.onMessage) {
+ return;
+ }
// Inbound
- handler = isQml ? that.onMessage : that.fromHtml;
if (on) {
- if (!that.hasEventBridge) {
- console.debug(that.buttonName, 'connecting', that.eventSignal());
- that.eventSignal().connect(handler);
- that.hasEventBridge = true;
+ if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) {
+ console.debug(that.buttonName, 'connecting', that.tablet.fromQml);
+ that.tablet.fromQml.connect(that.onMessage);
+ that.hasInboundQmlEventBridge = true;
+ } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) {
+ console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived);
+ that.tablet.webEventReceived.connect(that.fromHtml);
+ that.hasInboundHtmlEventBridge = true;
}
} else {
- if (that.hasEventBridge) {
- console.debug(that.buttonName, 'disconnecting', that.eventSignal());
- that.eventSignal().disconnect(handler);
- that.hasEventBridge = false;
+ if (that.hasInboundQmlEventBridge) {
+ console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml);
+ that.tablet.fromQml.disconnect(that.onMessage);
+ that.hasInboundQmlEventBridge = false;
+ }
+ if (that.hasInboundHtmlEventBridge) {
+ console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived);
+ that.tablet.webEventReceived.disconnect(that.fromHtml);
+ that.hasInboundHtmlEventBridge = false;
}
}
};
@@ -175,6 +303,7 @@ function AppUi(properties) {
} : that.ignore;
that.onScriptEnding = function onScriptEnding() {
// Close if necessary, clean up any remaining handlers, and remove the button.
+ GlobalServices.findableByChanged.disconnect(availabilityChanged);
if (that.isOpen) {
that.close();
}
@@ -185,10 +314,15 @@ function AppUi(properties) {
}
that.tablet.removeButton(that.button);
}
+ if (that.notificationPollTimeout) {
+ Script.clearInterval(that.notificationPollTimeout);
+ that.notificationPollTimeout = false;
+ }
};
// Set up the handlers.
that.tablet.screenChanged.connect(that.onScreenChanged);
that.button.clicked.connect(that.onClicked);
Script.scriptEnding.connect(that.onScriptEnding);
+ GlobalServices.findableByChanged.connect(availabilityChanged);
}
module.exports = AppUi;
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 5939b36438..b12191b00c 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -1,5 +1,5 @@
"use strict";
-/*jslint vars:true, plusplus:true, forin:true*/
+/* jslint vars:true, plusplus:true, forin:true */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// wallet.js
@@ -14,629 +14,562 @@
/* global getConnectionData */
(function () { // BEGIN LOCAL_SCOPE
- Script.include("/~/system/libraries/accountUtils.js");
- Script.include("/~/system/libraries/connectionUtils.js");
+Script.include("/~/system/libraries/accountUtils.js");
+Script.include("/~/system/libraries/connectionUtils.js");
+var AppUi = Script.require('appUi');
- var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
+var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
- // BEGIN AVATAR SELECTOR LOGIC
- var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
- var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
- var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
+// BEGIN AVATAR SELECTOR LOGIC
+var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
+var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
+var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
- var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
+var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
- function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
- overlays[key] = this;
- this.key = key;
- this.selected = false;
- this.hovering = false;
- this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
+ overlays[key] = this;
+ this.key = key;
+ this.selected = false;
+ this.hovering = false;
+ this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+}
+// Instance methods:
+ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
+ Overlays.deleteOverlay(this.activeOverlay);
+ delete overlays[this.key];
+};
+
+ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
+ Overlays.editOverlay(this.activeOverlay, properties);
+};
+
+function color(selected, hovering) {
+ var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
+ function scale(component) {
+ var delta = 0xFF - component;
+ return component;
}
- // Instance methods:
- ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
- Overlays.deleteOverlay(this.activeOverlay);
- delete overlays[this.key];
- };
-
- ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
- Overlays.editOverlay(this.activeOverlay, properties);
- };
-
- function color(selected, hovering) {
- var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
- function scale(component) {
- var delta = 0xFF - component;
- return component;
- }
- return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
- }
- // so we don't have to traverse the overlays to get the last one
- var lastHoveringId = 0;
- ExtendedOverlay.prototype.hover = function (hovering) {
- this.hovering = hovering;
- if (this.key === lastHoveringId) {
- if (hovering) {
- return;
- }
- lastHoveringId = 0;
- }
- this.editOverlay({ color: color(this.selected, hovering) });
+ return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
+}
+// so we don't have to traverse the overlays to get the last one
+var lastHoveringId = 0;
+ExtendedOverlay.prototype.hover = function (hovering) {
+ this.hovering = hovering;
+ if (this.key === lastHoveringId) {
if (hovering) {
- // un-hover the last hovering overlay
- if (lastHoveringId && lastHoveringId !== this.key) {
- ExtendedOverlay.get(lastHoveringId).hover(false);
- }
- lastHoveringId = this.key;
- }
- };
- ExtendedOverlay.prototype.select = function (selected) {
- if (this.selected === selected) {
return;
}
-
- this.editOverlay({ color: color(selected, this.hovering) });
- this.selected = selected;
- };
- // Class methods:
- var selectedId = false;
- ExtendedOverlay.isSelected = function (id) {
- return selectedId === id;
- };
- ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
- return overlays[key];
- };
- ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
- var key;
- for (key in overlays) {
- if (iterator(ExtendedOverlay.get(key))) {
- return;
- }
- }
- };
- ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
- if (lastHoveringId) {
+ lastHoveringId = 0;
+ }
+ this.editOverlay({ color: color(this.selected, hovering) });
+ if (hovering) {
+ // un-hover the last hovering overlay
+ if (lastHoveringId && lastHoveringId !== this.key) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
- };
+ lastHoveringId = this.key;
+ }
+};
+ExtendedOverlay.prototype.select = function (selected) {
+ if (this.selected === selected) {
+ return;
+ }
- // hit(overlay) on the one overlay intersected by pickRay, if any.
- // noHit() if no ExtendedOverlay was intersected (helps with hover)
- ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
- var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
- if (!pickedOverlay.intersects) {
- if (noHit) {
- return noHit();
- }
+ this.editOverlay({ color: color(selected, this.hovering) });
+ this.selected = selected;
+};
+// Class methods:
+var selectedId = false;
+ExtendedOverlay.isSelected = function (id) {
+ return selectedId === id;
+};
+ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
+ return overlays[key];
+};
+ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
+ var key;
+ for (key in overlays) {
+ if (iterator(ExtendedOverlay.get(key))) {
return;
}
- ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
- if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
- hit(overlay);
- return true;
- }
- });
- };
-
- function addAvatarNode(id) {
- return new ExtendedOverlay(id, "sphere", {
- drawInFront: true,
- solid: true,
- alpha: 0.8,
- color: color(false, false),
- ignoreRayIntersection: false
- });
}
-
- var pingPong = true;
- function updateOverlays() {
- var eye = Camera.position;
- AvatarList.getAvatarIdentifiers().forEach(function (id) {
- if (!id) {
- return; // don't update ourself, or avatars we're not interested in
- }
- var avatar = AvatarList.getAvatar(id);
- if (!avatar) {
- return; // will be deleted below if there had been an overlay.
- }
- var overlay = ExtendedOverlay.get(id);
- if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
- overlay = addAvatarNode(id);
- }
- var target = avatar.position;
- var distance = Vec3.distance(target, eye);
- var offset = 0.2;
- var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
- var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
- if (headIndex > 0) {
- offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
- }
-
- // move a bit in front, towards the camera
- target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
-
- // now bump it up a bit
- target.y = target.y + offset;
-
- overlay.ping = pingPong;
- overlay.editOverlay({
- color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
- position: target,
- dimensions: 0.032 * distance
- });
- });
- pingPong = !pingPong;
- ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
- if (overlay.ping === pingPong) {
- overlay.deleteOverlay();
- }
- });
- }
- function removeOverlays() {
- selectedId = false;
- lastHoveringId = 0;
- ExtendedOverlay.some(function (overlay) {
- overlay.deleteOverlay();
- });
+};
+ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
+ if (lastHoveringId) {
+ ExtendedOverlay.get(lastHoveringId).hover(false);
}
+};
- //
- // Clicks.
- //
- function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
- if (selectedId === id) {
- var message = {
- method: 'updateSelectedRecipientUsername',
- userName: username === "" ? "unknown username" : username
- };
- sendToQml(message);
+// hit(overlay) on the one overlay intersected by pickRay, if any.
+// noHit() if no ExtendedOverlay was intersected (helps with hover)
+ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
+ var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
+ if (!pickedOverlay.intersects) {
+ if (noHit) {
+ return noHit();
}
+ return;
}
- function handleClick(pickRay) {
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- var nextSelectedStatus = !overlay.selected;
- var avatarId = overlay.key;
- selectedId = nextSelectedStatus ? avatarId : false;
- if (nextSelectedStatus) {
- Users.requestUsernameFromID(avatarId);
- }
- var message = {
- method: 'selectRecipient',
- id: avatarId,
- isSelected: nextSelectedStatus,
- displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
- userName: ''
- };
- sendToQml(message);
-
- ExtendedOverlay.some(function (overlay) {
- var id = overlay.key;
- var selected = ExtendedOverlay.isSelected(id);
- overlay.select(selected);
- });
-
+ ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
+ if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
+ hit(overlay);
return true;
- });
- }
- function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
- if (!mousePressEvent.isLeftButton) {
- return;
}
- handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+ });
+};
+
+function addAvatarNode(id) {
+ return new ExtendedOverlay(id, "sphere", {
+ drawInFront: true,
+ solid: true,
+ alpha: 0.8,
+ color: color(false, false),
+ ignoreRayIntersection: false
+ });
+}
+
+var pingPong = true;
+function updateOverlays() {
+ var eye = Camera.position;
+ AvatarList.getAvatarIdentifiers().forEach(function (id) {
+ if (!id) {
+ return; // don't update ourself, or avatars we're not interested in
+ }
+ var avatar = AvatarList.getAvatar(id);
+ if (!avatar) {
+ return; // will be deleted below if there had been an overlay.
+ }
+ var overlay = ExtendedOverlay.get(id);
+ if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
+ overlay = addAvatarNode(id);
+ }
+ var target = avatar.position;
+ var distance = Vec3.distance(target, eye);
+ var offset = 0.2;
+ var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
+ var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
+ if (headIndex > 0) {
+ offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
+ }
+
+ // move a bit in front, towards the camera
+ target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
+
+ // now bump it up a bit
+ target.y = target.y + offset;
+
+ overlay.ping = pingPong;
+ overlay.editOverlay({
+ color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
+ position: target,
+ dimensions: 0.032 * distance
+ });
+ });
+ pingPong = !pingPong;
+ ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
+ if (overlay.ping === pingPong) {
+ overlay.deleteOverlay();
+ }
+ });
+}
+function removeOverlays() {
+ selectedId = false;
+ lastHoveringId = 0;
+ ExtendedOverlay.some(function (overlay) {
+ overlay.deleteOverlay();
+ });
+}
+
+//
+// Clicks.
+//
+function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
+ if (selectedId === id) {
+ var message = {
+ method: 'updateSelectedRecipientUsername',
+ userName: username === "" ? "unknown username" : username
+ };
+ ui.sendMessage(message);
}
- function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- overlay.hover(true);
- }, function () {
+}
+function handleClick(pickRay) {
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ var nextSelectedStatus = !overlay.selected;
+ var avatarId = overlay.key;
+ selectedId = nextSelectedStatus ? avatarId : false;
+ if (nextSelectedStatus) {
+ Users.requestUsernameFromID(avatarId);
+ }
+ var message = {
+ method: 'selectRecipient',
+ id: avatarId,
+ isSelected: nextSelectedStatus,
+ displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
+ userName: ''
+ };
+ ui.sendMessage(message);
+
+ ExtendedOverlay.some(function (overlay) {
+ var id = overlay.key;
+ var selected = ExtendedOverlay.isSelected(id);
+ overlay.select(selected);
+ });
+
+ return true;
+ });
+}
+function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
+ if (!mousePressEvent.isLeftButton) {
+ return;
+ }
+ handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+}
+function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ overlay.hover(true);
+ }, function () {
+ ExtendedOverlay.unHover();
+ });
+}
+
+// handy global to keep track of which hand is the mouse (if any)
+var currentHandPressed = 0;
+var TRIGGER_CLICK_THRESHOLD = 0.85;
+var TRIGGER_PRESS_THRESHOLD = 0.05;
+
+function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
+ var pickRay;
+ if (HMD.active) {
+ if (currentHandPressed !== 0) {
+ pickRay = controllerComputePickRay(currentHandPressed);
+ } else {
+ // nothing should hover, so
ExtendedOverlay.unHover();
+ return;
+ }
+ } else {
+ pickRay = Camera.computePickRay(event.x, event.y);
+ }
+ handleMouseMove(pickRay);
+}
+function handleTriggerPressed(hand, value) {
+ // The idea is if you press one trigger, it is the one
+ // we will consider the mouse. Even if the other is pressed,
+ // we ignore it until this one is no longer pressed.
+ var isPressed = value > TRIGGER_PRESS_THRESHOLD;
+ if (currentHandPressed === 0) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
+ }
+ if (currentHandPressed === hand) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
+ }
+ // otherwise, the other hand is still triggered
+ // so do nothing.
+}
+
+// We get mouseMoveEvents from the handControllers, via handControllerPointer.
+// But we don't get mousePressEvents.
+var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
+var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
+function controllerComputePickRay(hand) {
+ var controllerPose = getControllerWorldLocation(hand, true);
+ if (controllerPose.valid) {
+ return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
+ }
+}
+function makeClickHandler(hand) {
+ return function (clicked) {
+ if (clicked > TRIGGER_CLICK_THRESHOLD) {
+ var pickRay = controllerComputePickRay(hand);
+ handleClick(pickRay);
+ }
+ };
+}
+function makePressHandler(hand) {
+ return function (value) {
+ handleTriggerPressed(hand, value);
+ };
+}
+triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
+triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
+triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
+triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
+// END AVATAR SELECTOR LOGIC
+
+var sendMoneyRecipient;
+var sendMoneyParticleEffectUpdateTimer;
+var particleEffectTimestamp;
+var sendMoneyParticleEffect;
+var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250;
+var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000;
+var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8;
+var SEND_MONEY_PARTICLE_PROPERTIES = {
+ accelerationSpread: { x: 0, y: 0, z: 0 },
+ alpha: 1,
+ alphaFinish: 1,
+ alphaSpread: 0,
+ alphaStart: 1,
+ azimuthFinish: 0,
+ azimuthStart: -6,
+ color: { red: 143, green: 5, blue: 255 },
+ colorFinish: { red: 255, green: 0, blue: 204 },
+ colorSpread: { red: 0, green: 0, blue: 0 },
+ colorStart: { red: 0, green: 136, blue: 255 },
+ emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
+ emitDimensions: { x: 0, y: 0, z: 0 },
+ emitOrientation: { x: 0, y: 0, z: 0 },
+ emitRate: 4,
+ emitSpeed: 2.1,
+ emitterShouldTrail: true,
+ isEmitting: 1,
+ lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
+ lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1,
+ maxParticles: 20,
+ name: 'hfc-particles',
+ particleRadius: 0.2,
+ polarFinish: 0,
+ polarStart: 0,
+ radiusFinish: 0.05,
+ radiusSpread: 0,
+ radiusStart: 0.2,
+ speedSpread: 0,
+ textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
+ type: 'ParticleEffect'
+};
+
+var MS_PER_SEC = 1000;
+function updateSendMoneyParticleEffect() {
+ var timestampNow = Date.now();
+ if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) {
+ deleteSendMoneyParticleEffect();
+ return;
+ } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) {
+ Entities.editEntity(sendMoneyParticleEffect, {
+ isEmitting: 0
+ });
+ } else if (sendMoneyParticleEffect) {
+ var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position;
+ var distance = Vec3.distance(recipientPosition, MyAvatar.position);
+ var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
+ accel.y -= 3.0;
+ var life = Math.sqrt(2 * distance / Vec3.length(accel));
+ Entities.editEntity(sendMoneyParticleEffect, {
+ emitAcceleration: accel,
+ lifespan: life
});
}
+}
- // handy global to keep track of which hand is the mouse (if any)
- var currentHandPressed = 0;
- var TRIGGER_CLICK_THRESHOLD = 0.85;
- var TRIGGER_PRESS_THRESHOLD = 0.05;
-
- function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
- var pickRay;
- if (HMD.active) {
- if (currentHandPressed !== 0) {
- pickRay = controllerComputePickRay(currentHandPressed);
- } else {
- // nothing should hover, so
- ExtendedOverlay.unHover();
- return;
- }
- } else {
- pickRay = Camera.computePickRay(event.x, event.y);
- }
- handleMouseMove(pickRay);
+function deleteSendMoneyParticleEffect() {
+ if (sendMoneyParticleEffectUpdateTimer) {
+ Script.clearInterval(sendMoneyParticleEffectUpdateTimer);
+ sendMoneyParticleEffectUpdateTimer = null;
}
- function handleTriggerPressed(hand, value) {
- // The idea is if you press one trigger, it is the one
- // we will consider the mouse. Even if the other is pressed,
- // we ignore it until this one is no longer pressed.
- var isPressed = value > TRIGGER_PRESS_THRESHOLD;
- if (currentHandPressed === 0) {
- currentHandPressed = isPressed ? hand : 0;
- return;
- }
- if (currentHandPressed === hand) {
- currentHandPressed = isPressed ? hand : 0;
- return;
- }
- // otherwise, the other hand is still triggered
- // so do nothing.
+ if (sendMoneyParticleEffect) {
+ sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect);
}
+ sendMoneyRecipient = null;
+}
- // We get mouseMoveEvents from the handControllers, via handControllerPointer.
- // But we don't get mousePressEvents.
- var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
- var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
- function controllerComputePickRay(hand) {
- var controllerPose = getControllerWorldLocation(hand, true);
- if (controllerPose.valid) {
- return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
- }
+function onUsernameChanged() {
+ if (Account.username !== Settings.getValue("wallet/savedUsername")) {
+ Settings.setValue("wallet/autoLogout", false);
+ Settings.setValue("wallet/savedUsername", "");
}
- function makeClickHandler(hand) {
- return function (clicked) {
- if (clicked > TRIGGER_CLICK_THRESHOLD) {
- var pickRay = controllerComputePickRay(hand);
- handleClick(pickRay);
- }
- };
- }
- function makePressHandler(hand) {
- return function (value) {
- handleTriggerPressed(hand, value);
- };
- }
- triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
- triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
- triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
- triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
- // END AVATAR SELECTOR LOGIC
+}
- // Function Name: onButtonClicked()
- //
- // Description:
- // -Fired when the app button is pressed.
- //
- // Relevant Variables:
- // -WALLET_QML_SOURCE: The path to the Wallet QML
- // -onWalletScreen: true/false depending on whether we're looking at the app.
- var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
- var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
- var onWalletScreen = false;
- function onButtonClicked() {
- if (!tablet) {
- print("Warning in buttonClicked(): 'tablet' undefined!");
- return;
+// Function Name: fromQml()
+//
+// Description:
+// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML
+// in the format "{method, params}", like json-rpc. See also sendToQml().
+var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
+var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
+function fromQml(message) {
+ switch (message.method) {
+ case 'passphrasePopup_cancelClicked':
+ case 'needsLogIn_cancelClicked':
+ ui.close();
+ break;
+ case 'walletSetup_cancelClicked':
+ switch (message.referrer) {
+ case '': // User clicked "Wallet" app
+ case undefined:
+ case null:
+ ui.close();
+ break;
+ case 'purchases':
+ case 'marketplace cta':
+ case 'mainPage':
+ ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ default: // User needs to return to an individual marketplace item URL
+ ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
}
- if (onWalletScreen) {
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- tablet.loadQMLSource(WALLET_QML_SOURCE);
- }
- }
-
- // Function Name: sendToQml()
- //
- // Description:
- // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
- // the QML in the format "{method, params}", like json-rpc. See also fromQml().
- function sendToQml(message) {
- tablet.sendToQml(message);
- }
-
- var sendMoneyRecipient;
- var sendMoneyParticleEffectUpdateTimer;
- var particleEffectTimestamp;
- var sendMoneyParticleEffect;
- var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250;
- var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000;
- var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8;
- var SEND_MONEY_PARTICLE_PROPERTIES = {
- accelerationSpread: { x: 0, y: 0, z: 0 },
- alpha: 1,
- alphaFinish: 1,
- alphaSpread: 0,
- alphaStart: 1,
- azimuthFinish: 0,
- azimuthStart: -6,
- color: { red: 143, green: 5, blue: 255 },
- colorFinish: { red: 255, green: 0, blue: 204 },
- colorSpread: { red: 0, green: 0, blue: 0 },
- colorStart: { red: 0, green: 136, blue: 255 },
- emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
- emitDimensions: { x: 0, y: 0, z: 0 },
- emitOrientation: { x: 0, y: 0, z: 0 },
- emitRate: 4,
- emitSpeed: 2.1,
- emitterShouldTrail: true,
- isEmitting: 1,
- lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
- lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1,
- maxParticles: 20,
- name: 'hfc-particles',
- particleRadius: 0.2,
- polarFinish: 0,
- polarStart: 0,
- radiusFinish: 0.05,
- radiusSpread: 0,
- radiusStart: 0.2,
- speedSpread: 0,
- textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
- type: 'ParticleEffect'
- };
-
- function updateSendMoneyParticleEffect() {
- var timestampNow = Date.now();
- if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * 1000)) {
- deleteSendMoneyParticleEffect();
- return;
- } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) {
- Entities.editEntity(sendMoneyParticleEffect, {
- isEmitting: 0
- });
- } else if (sendMoneyParticleEffect) {
- var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position;
- var distance = Vec3.distance(recipientPosition, MyAvatar.position);
- var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
- accel.y -= 3.0;
- var life = Math.sqrt(2 * distance / Vec3.length(accel));
- Entities.editEntity(sendMoneyParticleEffect, {
- emitAcceleration: accel,
- lifespan: life
- });
- }
- }
-
- function deleteSendMoneyParticleEffect() {
- if (sendMoneyParticleEffectUpdateTimer) {
- Script.clearInterval(sendMoneyParticleEffectUpdateTimer);
- sendMoneyParticleEffectUpdateTimer = null;
- }
- if (sendMoneyParticleEffect) {
- sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect);
- }
- sendMoneyRecipient = null;
- }
-
- function onUsernameChanged() {
- if (Account.username !== Settings.getValue("wallet/savedUsername")) {
- Settings.setValue("wallet/autoLogout", false);
- Settings.setValue("wallet/savedUsername", "");
- }
- }
-
- // Function Name: fromQml()
- //
- // Description:
- // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML
- // in the format "{method, params}", like json-rpc. See also sendToQml().
- var isHmdPreviewDisabled = true;
- var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
-
- function fromQml(message) {
- switch (message.method) {
- case 'passphrasePopup_cancelClicked':
- case 'needsLogIn_cancelClicked':
- tablet.gotoHomeScreen();
- break;
- case 'walletSetup_cancelClicked':
- switch (message.referrer) {
- case '': // User clicked "Wallet" app
- case undefined:
- case null:
- tablet.gotoHomeScreen();
- break;
- case 'purchases':
- case 'marketplace cta':
- case 'mainPage':
- tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- default: // User needs to return to an individual marketplace item URL
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- }
- break;
- case 'needsLogIn_loginClicked':
- openLoginWindow();
- break;
- case 'disableHmdPreview':
- break; // do nothing here, handled in marketplaces.js
- case 'maybeEnableHmdPreview':
- break; // do nothing here, handled in marketplaces.js
- case 'passphraseReset':
- onButtonClicked();
- onButtonClicked();
- break;
- case 'walletReset':
- Settings.setValue("isFirstUseOfPurchases", true);
- onButtonClicked();
- onButtonClicked();
- break;
- case 'transactionHistory_linkClicked':
- tablet.gotoWebScreen(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'goToPurchases_fromWalletHome':
- case 'goToPurchases':
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- break;
- case 'goToMarketplaceMainPage':
- tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'goToMarketplaceItemPage':
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'refreshConnections':
- print('Refreshing Connections...');
- getConnectionData(false);
- break;
- case 'enable_ChooseRecipientNearbyMode':
- if (!isUpdateOverlaysWired) {
- Script.update.connect(updateOverlays);
- isUpdateOverlaysWired = true;
- }
- break;
- case 'disable_ChooseRecipientNearbyMode':
- if (isUpdateOverlaysWired) {
- Script.update.disconnect(updateOverlays);
- isUpdateOverlaysWired = false;
- }
- removeOverlays();
- break;
- case 'sendAsset_sendPublicly':
- if (message.assetName === "") {
- deleteSendMoneyParticleEffect();
- sendMoneyRecipient = message.recipient;
- var amount = message.amount;
- var props = SEND_MONEY_PARTICLE_PROPERTIES;
- props.parentID = MyAvatar.sessionUUID;
- props.position = MyAvatar.position;
- props.position.y += 0.2;
- if (message.effectImage) {
- props.textures = message.effectImage;
- }
- sendMoneyParticleEffect = Entities.addEntity(props, true);
- particleEffectTimestamp = Date.now();
- updateSendMoneyParticleEffect();
- sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE);
- }
- break;
- case 'transactionHistory_goToBank':
- if (Account.metaverseServerURL.indexOf("staging") >= 0) {
- Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging.
- } else {
- Window.location = "hifi://BankOfHighFidelity";
- }
- break;
- case 'wallet_availableUpdatesReceived':
- // NOP
- break;
- case 'http.request':
- // Handled elsewhere, don't log.
- break;
- default:
- print('Unrecognized message from QML:', JSON.stringify(message));
- }
- }
-
- // Function Name: wireEventBridge()
- //
- // Description:
- // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
- // disable to event bridge.
- //
- // Relevant Variables:
- // -hasEventBridge: true/false depending on whether we've already connected the event bridge.
- var hasEventBridge = false;
- function wireEventBridge(on) {
- if (!tablet) {
- print("Warning in wireEventBridge(): 'tablet' undefined!");
- return;
- }
- if (on) {
- if (!hasEventBridge) {
- tablet.fromQml.connect(fromQml);
- hasEventBridge = true;
- }
- } else {
- if (hasEventBridge) {
- tablet.fromQml.disconnect(fromQml);
- hasEventBridge = false;
- }
- }
- }
-
- // Function Name: onTabletScreenChanged()
- //
- // Description:
- // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
- // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
- function onTabletScreenChanged(type, url) {
- onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE);
- wireEventBridge(onWalletScreen);
- // Change button to active when window is first openend, false otherwise.
- if (button) {
- button.editProperties({ isActive: onWalletScreen });
- }
-
- if (onWalletScreen) {
- if (!isWired) {
- Users.usernameFromIDReply.connect(usernameFromIDReply);
- Controller.mousePressEvent.connect(handleMouseEvent);
- Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
- triggerMapping.enable();
- triggerPressMapping.enable();
- }
- isWired = true;
- } else {
- off();
- }
- }
-
- //
- // Manage the connection between the button and the window.
- //
- var button;
- var buttonName = "WALLET";
- var tablet = null;
- var walletEnabled = Settings.getValue("commerce", true);
- function startup() {
- GlobalServices.myUsernameChanged.connect(onUsernameChanged);
- if (walletEnabled) {
- tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- button = tablet.addButton({
- text: buttonName,
- icon: "icons/tablet-icons/wallet-i.svg",
- activeIcon: "icons/tablet-icons/wallet-a.svg",
- sortOrder: 10
- });
- button.clicked.connect(onButtonClicked);
- tablet.screenChanged.connect(onTabletScreenChanged);
- }
- }
- var isWired = false;
- var isUpdateOverlaysWired = false;
- function off() {
- if (isWired) {
- Users.usernameFromIDReply.disconnect(usernameFromIDReply);
- Controller.mousePressEvent.disconnect(handleMouseEvent);
- Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
- triggerMapping.disable();
- triggerPressMapping.disable();
-
- isWired = false;
+ break;
+ case 'needsLogIn_loginClicked':
+ openLoginWindow();
+ break;
+ case 'disableHmdPreview':
+ break; // do nothing here, handled in marketplaces.js
+ case 'maybeEnableHmdPreview':
+ break; // do nothing here, handled in marketplaces.js
+ case 'transactionHistory_linkClicked':
+ ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'goToPurchases_fromWalletHome':
+ case 'goToPurchases':
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ break;
+ case 'goToMarketplaceMainPage':
+ ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'goToMarketplaceItemPage':
+ ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'refreshConnections':
+ print('Refreshing Connections...');
+ getConnectionData(false);
+ break;
+ case 'enable_ChooseRecipientNearbyMode':
+ if (!isUpdateOverlaysWired) {
+ Script.update.connect(updateOverlays);
+ isUpdateOverlaysWired = true;
}
+ break;
+ case 'disable_ChooseRecipientNearbyMode':
if (isUpdateOverlaysWired) {
Script.update.disconnect(updateOverlays);
isUpdateOverlaysWired = false;
}
removeOverlays();
- }
- function shutdown() {
- GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
- button.clicked.disconnect(onButtonClicked);
- tablet.removeButton(button);
- deleteSendMoneyParticleEffect();
- if (tablet) {
- tablet.screenChanged.disconnect(onTabletScreenChanged);
- if (onWalletScreen) {
- tablet.gotoHomeScreen();
+ break;
+ case 'sendAsset_sendPublicly':
+ if (message.assetName === "") {
+ deleteSendMoneyParticleEffect();
+ sendMoneyRecipient = message.recipient;
+ var amount = message.amount;
+ var props = SEND_MONEY_PARTICLE_PROPERTIES;
+ props.parentID = MyAvatar.sessionUUID;
+ props.position = MyAvatar.position;
+ props.position.y += 0.2;
+ if (message.effectImage) {
+ props.textures = message.effectImage;
}
+ sendMoneyParticleEffect = Entities.addEntity(props, true);
+ particleEffectTimestamp = Date.now();
+ updateSendMoneyParticleEffect();
+ sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE);
}
- off();
+ break;
+ case 'transactionHistory_goToBank':
+ if (Account.metaverseServerURL.indexOf("staging") >= 0) {
+ Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging.
+ } else {
+ Window.location = "hifi://BankOfHighFidelity";
+ }
+ break;
+ case 'wallet_availableUpdatesReceived':
+ // NOP
+ break;
+ case 'http.request':
+ // Handled elsewhere, don't log.
+ break;
+ default:
+ print('Unrecognized message from QML:', JSON.stringify(message));
+ }
+}
+
+function walletOpened() {
+ Users.usernameFromIDReply.connect(usernameFromIDReply);
+ Controller.mousePressEvent.connect(handleMouseEvent);
+ Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+ triggerMapping.enable();
+ triggerPressMapping.enable();
+}
+
+function walletClosed() {
+ off();
+}
+
+//
+// Manage the connection between the button and the window.
+//
+var DEVELOPER_MENU = "Developer";
+var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester";
+var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
+function installMarketplaceItemTester() {
+ if (!Menu.menuExists(DEVELOPER_MENU)) {
+ Menu.addMenu(DEVELOPER_MENU);
+ }
+ if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) {
+ Menu.addMenuItem({ menuName: DEVELOPER_MENU,
+ menuItemName: MARKETPLACE_ITEM_TESTER_LABEL,
+ isCheckable: false })
}
- //
- // Run the functions.
- //
- startup();
- Script.scriptEnding.connect(shutdown);
+ Menu.menuItemEvent.connect(function (menuItem) {
+ if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) {
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ tablet.loadQMLSource(MARKETPLACE_ITEM_TESTER_QML_SOURCE);
+ }
+ });
+}
+function uninstallMarketplaceItemTester() {
+ if (Menu.menuExists(DEVELOPER_MENU) &&
+ Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)
+ ) {
+ Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL);
+ }
+}
+
+var BUTTON_NAME = "WALLET";
+var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
+var ui;
+function startup() {
+ ui = new AppUi({
+ buttonName: BUTTON_NAME,
+ sortOrder: 10,
+ home: WALLET_QML_SOURCE,
+ onOpened: walletOpened,
+ onClosed: walletClosed,
+ onMessage: fromQml
+ });
+ GlobalServices.myUsernameChanged.connect(onUsernameChanged);
+ installMarketplaceItemTester();
+}
+
+var isUpdateOverlaysWired = false;
+function off() {
+ Users.usernameFromIDReply.disconnect(usernameFromIDReply);
+ Controller.mousePressEvent.disconnect(handleMouseEvent);
+ Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
+ triggerMapping.disable();
+ triggerPressMapping.disable();
+
+ if (isUpdateOverlaysWired) {
+ Script.update.disconnect(updateOverlays);
+ isUpdateOverlaysWired = false;
+ }
+ removeOverlays();
+}
+
+function shutdown() {
+ GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
+ deleteSendMoneyParticleEffect();
+ uninstallMarketplaceItemTester();
+ off();
+}
+
+//
+// Run the functions.
+//
+startup();
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
index 3a33082f64..bc09ebee7a 100644
--- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
+++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
@@ -37,7 +37,7 @@
this.highlightedEntities = [];
this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
- 120,
+ 480,
this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100);
diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js
index 2bdd89f141..d590545532 100644
--- a/scripts/system/controllers/controllerModules/inEditMode.js
+++ b/scripts/system/controllers/controllerModules/inEditMode.js
@@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js");
this.reticleMaxY;
this.parameters = makeDispatcherModuleParameters(
- 200,
+ 160,
this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
[],
100,
diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js
index 02863cf935..7b78d5e1c4 100644
--- a/scripts/system/controllers/controllerModules/inVREditMode.js
+++ b/scripts/system/controllers/controllerModules/inVREditMode.js
@@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.disableModules = false;
var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed.
this.parameters = makeDispatcherModuleParameters(
- 240, // Not too high otherwise the tablet laser doesn't work.
+ 200, // Not too high otherwise the tablet laser doesn't work.
this.hand === RIGHT_HAND
? ["rightHand", "rightHandEquip", "rightHandTrigger"]
: ["leftHand", "leftHandEquip", "leftHandTrigger"],
diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
index 27c1b458b8..a8de76aebd 100644
--- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
@@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.hapticTargetID = null;
this.parameters = makeDispatcherModuleParameters(
- 140,
+ 500,
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100);
diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js
index 366fcd3032..962ae89bb9 100644
--- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js
+++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js
@@ -21,7 +21,7 @@
this.hyperlink = "";
this.parameters = makeDispatcherModuleParameters(
- 125,
+ 485,
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100);
diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
index f805dbf60e..cc88371441 100644
--- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
@@ -57,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js");
this.cloneAllowed = true;
this.parameters = makeDispatcherModuleParameters(
- 140,
+ 500,
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100);
diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js
index f1126dedc3..6a9cd9fbcd 100644
--- a/scripts/system/controllers/controllerModules/nearTrigger.js
+++ b/scripts/system/controllers/controllerModules/nearTrigger.js
@@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.startSent = false;
this.parameters = makeDispatcherModuleParameters(
- 120,
+ 480,
this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"],
[],
100);
diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
index 4e36355621..2412e2fa1c 100644
--- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
+++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
@@ -121,7 +121,7 @@ Script.include("/~/system/libraries/controllers.js");
controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE;
var allowThisModule = !otherModuleRunning || isTriggerPressed;
- if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) {
+ if ((allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) && !this.grabModuleWantsNearbyOverlay(controllerData)) {
this.updateAllwaysOn();
if (isTriggerPressed) {
this.dominantHandOverride = true; // Override dominant hand.
diff --git a/scripts/system/edit.js b/scripts/system/edit.js
index 88ae2852be..b1f01e1ea9 100644
--- a/scripts/system/edit.js
+++ b/scripts/system/edit.js
@@ -2134,9 +2134,7 @@ var PropertiesTool = function (opts) {
var onWebEventReceived = function(data) {
try {
data = JSON.parse(data);
- }
- catch(e) {
- print('Edit.js received web event that was not valid json.');
+ } catch(e) {
return;
}
var i, properties, dY, diff, newPosition;
diff --git a/scripts/system/help.js b/scripts/system/help.js
index aaeb82721c..40bbf6dbe2 100644
--- a/scripts/system/help.js
+++ b/scripts/system/help.js
@@ -1,5 +1,5 @@
"use strict";
-
+/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// help.js
// scripts/system/
@@ -12,50 +12,18 @@
//
/* globals Tablet, Script, HMD, Controller, Menu */
-(function() { // BEGIN LOCAL_SCOPE
-
- var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
- var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html";
- var buttonName = "HELP";
- var onHelpScreen = false;
- var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- var button = tablet.addButton({
- icon: "icons/tablet-icons/help-i.svg",
- activeIcon: "icons/tablet-icons/help-a.svg",
- text: buttonName,
- sortOrder: 6
+(function () { // BEGIN LOCAL_SCOPE
+var AppUi = Script.require('appUi');
+
+var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html";
+var HELP_BUTTON_NAME = "HELP";
+var ui;
+function startup() {
+ ui = new AppUi({
+ buttonName: HELP_BUTTON_NAME,
+ sortOrder: 6,
+ home: HELP_URL
});
-
- var enabled = false;
- function onClicked() {
- if (onHelpScreen) {
- tablet.gotoHomeScreen();
- } else {
- if (HMD.tabletID) {
- Entities.editEntity(HMD.tabletID, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})});
- }
- Menu.triggerOption('Help...');
- onHelpScreen = true;
- }
- }
-
- function onScreenChanged(type, url) {
- onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0);
- button.editProperties({ isActive: onHelpScreen });
- }
-
- button.clicked.connect(onClicked);
- tablet.screenChanged.connect(onScreenChanged);
-
- Script.scriptEnding.connect(function () {
- if (onHelpScreen) {
- tablet.gotoHomeScreen();
- }
- button.clicked.disconnect(onClicked);
- tablet.screenChanged.disconnect(onScreenChanged);
- if (tablet) {
- tablet.removeButton(button);
- }
- });
-
+}
+startup();
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js
index 9cfdf6df22..7cc5937536 100644
--- a/scripts/system/html/js/entityList.js
+++ b/scripts/system/html/js/entityList.js
@@ -27,8 +27,11 @@ const COMPARE_ASCENDING = function(a, b) {
return -1;
} else if (va > vb) {
return 1;
+ } else if (a.id < b.id) {
+ return -1;
}
- return 0;
+
+ return 1;
}
const COMPARE_DESCENDING = function(a, b) {
return COMPARE_ASCENDING(b, a);
@@ -161,7 +164,10 @@ function loaded() {
selectedEntities.forEach(function(entityID) {
if (selection.indexOf(entityID) === -1) {
- entitiesByID[entityID].el.className = '';
+ let entity = entitiesByID[entityID];
+ if (entity !== undefined) {
+ entity.el.className = '';
+ }
}
});
@@ -223,15 +229,15 @@ function loaded() {
type: type,
url: filename,
fullUrl: entity.url,
- locked: entity.locked ? LOCKED_GLYPH : null,
- visible: entity.visible ? VISIBLE_GLYPH : null,
- verticesCount: displayIfNonZero(entity.verticesCount),
- texturesCount: displayIfNonZero(entity.texturesCount),
- texturesSize: decimalMegabytes(entity.texturesSize),
- hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null,
- isBaked: entity.isBaked ? BAKED_GLYPH : null,
- drawCalls: displayIfNonZero(entity.drawCalls),
- hasScript: entity.hasScript ? SCRIPT_GLYPH : null,
+ locked: entity.locked,
+ visible: entity.visible,
+ verticesCount: entity.verticesCount,
+ texturesCount: entity.texturesCount,
+ texturesSize: entity.texturesSize,
+ hasTransparent: entity.hasTransparent,
+ isBaked: entity.isBaked,
+ drawCalls: entity.drawCalls,
+ hasScript: entity.hasScript,
}
entities.push(entityData);
@@ -259,15 +265,15 @@ function loaded() {
addColumn('type', entity.type);
addColumn('name', entity.name);
addColumn('url', entity.url);
- addColumnHTML('locked glyph', entity.locked);
- addColumnHTML('visible glyph', entity.visible);
- addColumn('verticesCount', entity.verticesCount);
- addColumn('texturesCount', entity.texturesCount);
- addColumn('texturesSize', entity.texturesSize);
- addColumnHTML('hasTransparent glyph', entity.hasTransparent);
- addColumnHTML('isBaked glyph', entity.isBaked);
- addColumn('drawCalls', entity.drawCalls);
- addColumn('hasScript glyph', entity.hasScript);
+ addColumnHTML('locked glyph', entity.locked ? LOCKED_GLYPH : null);
+ addColumnHTML('visible glyph', entity.visible ? VISIBLE_GLYPH : null);
+ addColumn('verticesCount', displayIfNonZero(entity.verticesCount));
+ addColumn('texturesCount', displayIfNonZero(entity.texturesCount));
+ addColumn('texturesSize', decimalMegabytes(entity.texturesSize));
+ addColumnHTML('hasTransparent glyph', entity.hasTransparent ? TRANSPARENCY_GLYPH : null);
+ addColumnHTML('isBaked glyph', entity.isBaked ? BAKED_GLYPH : null);
+ addColumn('drawCalls', displayIfNonZero(entity.drawCalls));
+ addColumn('hasScript glyph', entity.hasScript ? SCRIPT_GLYPH : null);
row.addEventListener('click', onRowClicked);
row.addEventListener('dblclick', onRowDoubleClicked);
@@ -385,15 +391,18 @@ function loaded() {
let notFound = false;
selectedEntities.forEach(function(id) {
- entitiesByID[id].el.className = '';
+ let entity = entitiesByID[id];
+ if (entity !== undefined) {
+ entity.el.className = '';
+ }
});
selectedEntities = [];
for (let i = 0; i < selectedIDs.length; i++) {
let id = selectedIDs[i];
selectedEntities.push(id);
- if (id in entitiesByID) {
- let entity = entitiesByID[id];
+ let entity = entitiesByID[id];
+ if (entity !== undefined) {
entity.el.className = 'selected';
} else {
notFound = true;
diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js
index 7eece890c9..3239105254 100644
--- a/scripts/system/html/js/marketplacesInject.js
+++ b/scripts/system/html/js/marketplacesInject.js
@@ -1,3 +1,5 @@
+/* global $, window, MutationObserver */
+
//
// marketplacesInject.js
//
@@ -11,7 +13,6 @@
//
(function () {
-
// Event bridge messages.
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
var CLARA_IO_STATUS = "CLARA.IO STATUS";
@@ -24,7 +25,7 @@
var canWriteAssets = false;
var xmlHttpRequest = null;
- var isPreparing = false; // Explicitly track download request status.
+ var isPreparing = false; // Explicitly track download request status.
var commerceMode = false;
var userIsLoggedIn = false;
@@ -33,7 +34,6 @@
var messagesWaiting = false;
function injectCommonCode(isDirectoryPage) {
-
// Supporting styles from marketplaces.css.
// Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain.
$("head").append(
@@ -74,7 +74,9 @@
(document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?");
});
$("#all-markets").on("click", function () {
- EventBridge.emitWebEvent(GOTO_DIRECTORY);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: GOTO_DIRECTORY
+ }));
});
}
@@ -94,11 +96,11 @@
});
}
- emitWalletSetupEvent = function() {
+ var emitWalletSetupEvent = function () {
EventBridge.emitWebEvent(JSON.stringify({
type: "WALLET_SETUP"
}));
- }
+ };
function maybeAddSetupWalletButton() {
if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) {
@@ -285,7 +287,7 @@
$(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6');
$(this).closest('.col-xs-3').attr("class", 'col-xs-6');
- var priceElement = $(this).find('.price')
+ var priceElement = $(this).find('.price');
priceElement.css({
"padding": "3px 5px",
"height": "40px",
@@ -355,12 +357,12 @@
function injectAddScrollbarToCategories() {
$('#categories-dropdown').on('show.bs.dropdown', function () {
$('body > div.container').css('display', 'none')
- $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' })
+ $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' });
});
$('#categories-dropdown').on('hide.bs.dropdown', function () {
- $('body > div.container').css('display', '')
- $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' })
+ $('body > div.container').css('display', '');
+ $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' });
});
}
@@ -382,7 +384,6 @@
mutations.forEach(function (mutation) {
injectBuyButtonOnMainPage();
});
- //observer.disconnect();
});
var config = { attributes: true, childList: true, characterData: true };
observer.observe(target, config);
@@ -451,8 +452,8 @@
"itemPage",
urlParams.get('edition'),
type);
- }
- });
+ }
+ });
maybeAddPurchasesButton();
}
}
@@ -503,127 +504,142 @@
$(".top-title .col-sm-4").append(downloadContainer);
downloadContainer.append(downloadFBX);
}
+ }
+ }
- // Automatic download to High Fidelity.
- function startAutoDownload() {
+ // Automatic download to High Fidelity.
+ function startAutoDownload() {
+ // One file request at a time.
+ if (isPreparing) {
+ console.log("WARNING: Clara.io FBX: Prepare only one download at a time");
+ return;
+ }
- // One file request at a time.
- if (isPreparing) {
- console.log("WARNING: Clara.io FBX: Prepare only one download at a time");
- return;
- }
+ // User must be able to write to Asset Server.
+ if (!canWriteAssets) {
+ console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server");
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: WARN_USER_NO_PERMISSIONS
+ }));
+ return;
+ }
- // User must be able to write to Asset Server.
- if (!canWriteAssets) {
- console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server");
- EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS);
- return;
- }
+ // User must be logged in.
+ var loginButton = $("#topnav a[href='/signup']");
+ if (loginButton.length > 0) {
+ loginButton[0].click();
+ return;
+ }
- // User must be logged in.
- var loginButton = $("#topnav a[href='/signup']");
- if (loginButton.length > 0) {
- loginButton[0].click();
- return;
- }
+ // Obtain zip file to download for requested asset.
+ // Reference: https://clara.io/learn/sdk/api/export
- // Obtain zip file to download for requested asset.
- // Reference: https://clara.io/learn/sdk/api/export
+ //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL";
+ // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
+ // be successful in generating zip files.
+ var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL";
- //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL";
- // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
- // be successful in generating zip files.
- var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL";
+ var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1];
+ var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid);
- var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1];
- var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid);
+ xmlHttpRequest = new XMLHttpRequest();
+ var responseTextIndex = 0;
+ var zipFileURL = "";
- xmlHttpRequest = new XMLHttpRequest();
- var responseTextIndex = 0;
- var zipFileURL = "";
+ xmlHttpRequest.onreadystatechange = function () {
+ // Messages are appended to responseText; process the new ones.
+ var message = this.responseText.slice(responseTextIndex);
+ var statusMessage = "";
- xmlHttpRequest.onreadystatechange = function () {
- // Messages are appended to responseText; process the new ones.
- var message = this.responseText.slice(responseTextIndex);
- var statusMessage = "";
+ if (isPreparing) { // Ignore messages in flight after finished/cancelled.
+ var lines = message.split(/[\n\r]+/);
- if (isPreparing) { // Ignore messages in flight after finished/cancelled.
- var lines = message.split(/[\n\r]+/);
+ for (var i = 0, length = lines.length; i < length; i++) {
+ if (lines[i].slice(0, 5) === "data:") {
+ // Parse line.
+ var data;
+ try {
+ data = JSON.parse(lines[i].slice(5));
+ }
+ catch (e) {
+ data = {};
+ }
- for (var i = 0, length = lines.length; i < length; i++) {
- if (lines[i].slice(0, 5) === "data:") {
- // Parse line.
- var data;
- try {
- data = JSON.parse(lines[i].slice(5));
- }
- catch (e) {
- data = {};
- }
+ // Extract status message.
+ if (data.hasOwnProperty("message") && data.message !== null) {
+ statusMessage = data.message;
+ console.log("Clara.io FBX: " + statusMessage);
+ }
- // Extract status message.
- if (data.hasOwnProperty("message") && data.message !== null) {
- statusMessage = data.message;
- console.log("Clara.io FBX: " + statusMessage);
- }
-
- // Extract zip file URL.
- if (data.hasOwnProperty("files") && data.files.length > 0) {
- zipFileURL = data.files[0].url;
- if (zipFileURL.slice(-4) !== ".zip") {
- console.log(JSON.stringify(data)); // Data for debugging.
- }
- }
+ // Extract zip file URL.
+ if (data.hasOwnProperty("files") && data.files.length > 0) {
+ zipFileURL = data.files[0].url;
+ if (zipFileURL.slice(-4) !== ".zip") {
+ console.log(JSON.stringify(data)); // Data for debugging.
}
}
-
- if (statusMessage !== "") {
- // Update the UI with the most recent status message.
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
- }
}
-
- responseTextIndex = this.responseText.length;
- };
-
- // Note: onprogress doesn't have computable total length so can't use it to determine % complete.
-
- xmlHttpRequest.onload = function () {
- var statusMessage = "";
-
- if (!isPreparing) {
- return;
- }
-
- isPreparing = false;
-
- var HTTP_OK = 200;
- if (this.status !== HTTP_OK) {
- statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText;
- console.log("ERROR: Clara.io FBX: " + statusMessage);
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
- } else if (zipFileURL.slice(-4) !== ".zip") {
- statusMessage = "Error creating zip file for download.";
- console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL);
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
- } else {
- EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL);
- console.log("Clara.io FBX: File download initiated for " + zipFileURL);
- }
-
- xmlHttpRequest = null;
}
- isPreparing = true;
-
- console.log("Clara.io FBX: Request zip file for " + uuid);
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download");
-
- xmlHttpRequest.open("POST", url, true);
- xmlHttpRequest.setRequestHeader("Accept", "text/event-stream");
- xmlHttpRequest.send();
+ if (statusMessage !== "") {
+ // Update the UI with the most recent status message.
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: statusMessage
+ }));
+ }
}
+
+ responseTextIndex = this.responseText.length;
+ };
+
+ // Note: onprogress doesn't have computable total length so can't use it to determine % complete.
+
+ xmlHttpRequest.onload = function () {
+ var statusMessage = "";
+
+ if (!isPreparing) {
+ return;
+ }
+
+ isPreparing = false;
+
+ var HTTP_OK = 200;
+ if (this.status !== HTTP_OK) {
+ statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText;
+ console.log("ERROR: Clara.io FBX: " + statusMessage);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: statusMessage
+ }));
+ } else if (zipFileURL.slice(-4) !== ".zip") {
+ statusMessage = "Error creating zip file for download.";
+ console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: (statusMessage + ": " + zipFileURL)
+ }));
+ } else {
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_DOWNLOAD
+ }));
+ console.log("Clara.io FBX: File download initiated for " + zipFileURL);
+ }
+
+ xmlHttpRequest = null;
}
+
+ isPreparing = true;
+
+ console.log("Clara.io FBX: Request zip file for " + uuid);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: "Initiating download"
+ }));
+
+ xmlHttpRequest.open("POST", url, true);
+ xmlHttpRequest.setRequestHeader("Accept", "text/event-stream");
+ xmlHttpRequest.send();
}
function injectClaraCode() {
@@ -663,7 +679,9 @@
updateClaraCodeInterval = undefined;
});
- EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: QUERY_CAN_WRITE_ASSETS
+ }));
}
function cancelClaraDownload() {
@@ -673,7 +691,9 @@
xmlHttpRequest.abort();
xmlHttpRequest = null;
console.log("Clara.io FBX: File download cancelled");
- EventBridge.emitWebEvent(CLARA_IO_CANCELLED_DOWNLOAD);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_CANCELLED_DOWNLOAD
+ }));
}
}
@@ -708,25 +728,22 @@
function onLoad() {
EventBridge.scriptEventReceived.connect(function (message) {
- if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) {
- canWriteAssets = message.slice(-4) === "true";
- } else if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) {
+ message = JSON.parse(message);
+ if (message.type === CAN_WRITE_ASSETS) {
+ canWriteAssets = message.canWriteAssets;
+ } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) {
cancelClaraDownload();
- } else {
- var parsedJsonMessage = JSON.parse(message);
-
- if (parsedJsonMessage.type === "marketplaces") {
- if (parsedJsonMessage.action === "commerceSetting") {
- commerceMode = !!parsedJsonMessage.data.commerceMode;
- userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn;
- walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup;
- marketplaceBaseURL = parsedJsonMessage.data.metaverseServerURL;
- if (marketplaceBaseURL.indexOf('metaverse.') !== -1) {
- marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', '');
- }
- messagesWaiting = parsedJsonMessage.data.messagesWaiting;
- injectCode();
+ } else if (message.type === "marketplaces") {
+ if (message.action === "commerceSetting") {
+ commerceMode = !!message.data.commerceMode;
+ userIsLoggedIn = !!message.data.userIsLoggedIn;
+ walletNeedsSetup = !!message.data.walletNeedsSetup;
+ marketplaceBaseURL = message.data.metaverseServerURL;
+ if (marketplaceBaseURL.indexOf('metaverse.') !== -1) {
+ marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', '');
}
+ messagesWaiting = message.data.messagesWaiting;
+ injectCode();
}
}
});
@@ -739,6 +756,6 @@
}
// Load / unload.
- window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
- window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed
+ window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
+ window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed
}());
diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js
index 57726f397b..0c786dad87 100644
--- a/scripts/system/interstitialPage.js
+++ b/scripts/system/interstitialPage.js
@@ -140,7 +140,6 @@
localPosition: { x: 0.0 , y: -1.5, z: -0.3 },
url: Script.resourcesPath() + "images/interstitialPage/goTo_button.png",
alpha: 1,
- dimensions: { x: 1.5, y: 1.0 },
visible: isVisible,
emissive: true,
ignoreRayIntersection: false,
diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js
index 5bca58b1ac..1fc0f0611c 100644
--- a/scripts/system/libraries/entitySelectionTool.js
+++ b/scripts/system/libraries/entitySelectionTool.js
@@ -29,17 +29,6 @@ Script.include([
SelectionManager = (function() {
var that = {};
- /**
- * @description Removes known to be broken properties from a properties object
- * @param properties
- * @return properties
- */
- var fixRemoveBrokenProperties = function (properties) {
- // Reason: Entity property is always set to 0,0,0 which causes it to override angularVelocity (see MS17131)
- delete properties.localAngularVelocity;
- return properties;
- }
-
// FUNCTION: SUBSCRIBE TO UPDATE MESSAGES
function subscribeToUpdateMessages() {
Messages.subscribe("entityToolUpdates");
@@ -130,7 +119,7 @@ SelectionManager = (function() {
that.savedProperties = {};
for (var i = 0; i < that.selections.length; i++) {
var entityID = that.selections[i];
- that.savedProperties[entityID] = fixRemoveBrokenProperties(Entities.getEntityProperties(entityID));
+ that.savedProperties[entityID] = Entities.getEntityProperties(entityID);
}
};
@@ -258,7 +247,7 @@ SelectionManager = (function() {
var originalEntityID = entitiesToDuplicate[i];
var properties = that.savedProperties[originalEntityID];
if (properties === undefined) {
- properties = fixRemoveBrokenProperties(Entities.getEntityProperties(originalEntityID));
+ properties = Entities.getEntityProperties(originalEntityID);
}
if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) {
if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) {
diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js
index 3a114f23c7..6f62742e8f 100644
--- a/scripts/system/libraries/gridTool.js
+++ b/scripts/system/libraries/gridTool.js
@@ -267,7 +267,6 @@ GridTool = function(opts) {
try {
data = JSON.parse(data);
} catch (e) {
- print("gridTool.js: Error parsing JSON: " + e.name + " data " + data);
return;
}
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 7b4f05193f..cc5ff99673 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -16,1147 +16,1137 @@
var selectionDisplay = null; // for gridTool.js to ignore
(function () { // BEGIN LOCAL_SCOPE
+var AppUi = Script.require('appUi');
+Script.include("/~/system/libraries/gridTool.js");
+Script.include("/~/system/libraries/connectionUtils.js");
- Script.include("/~/system/libraries/WebTablet.js");
- Script.include("/~/system/libraries/gridTool.js");
- Script.include("/~/system/libraries/connectionUtils.js");
+var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
+var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
+var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
+var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
+var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
+var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
+var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
+var METAVERSE_SERVER_URL = Account.metaverseServerURL;
+var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
- var METAVERSE_SERVER_URL = Account.metaverseServerURL;
- var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
- var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
- var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
- var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
- var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
- var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
- var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
- var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
- var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
+// Event bridge messages.
+var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
+var CLARA_IO_STATUS = "CLARA.IO STATUS";
+var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
+var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
+var GOTO_DIRECTORY = "GOTO_DIRECTORY";
+var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
+var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
+var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
- var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
- // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
+var CLARA_DOWNLOAD_TITLE = "Preparing Download";
+var messageBox = null;
+var isDownloadBeingCancelled = false;
- // Event bridge messages.
- var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
- var CLARA_IO_STATUS = "CLARA.IO STATUS";
- var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
- var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
- var GOTO_DIRECTORY = "GOTO_DIRECTORY";
- var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
- var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
- var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
+var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel
+var NO_BUTTON = 0; // QMessageBox::NoButton
- var CLARA_DOWNLOAD_TITLE = "Preparing Download";
- var messageBox = null;
- var isDownloadBeingCancelled = false;
+var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
- var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel
- var NO_BUTTON = 0; // QMessageBox::NoButton
-
- var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
-
- function onMessageBoxClosed(id, button) {
- if (id === messageBox && button === CANCEL_BUTTON) {
- isDownloadBeingCancelled = true;
- messageBox = null;
- tablet.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD);
- }
- }
-
- var onMarketplaceScreen = false;
- var onCommerceScreen = false;
-
- var debugCheckout = false;
- var debugError = false;
- function showMarketplace() {
- if (!debugCheckout) {
- UserActivityLogger.openedMarketplace();
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- } else {
- tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
- sendToQml({
- method: 'updateCheckoutQML', params: {
- itemId: '424611a2-73d0-4c03-9087-26a6a279257b',
- itemName: '2018-02-15 Finnegon',
- itemPrice: (debugError ? 10 : 3),
- itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst',
- categories: ["Miscellaneous"]
- }
- });
- }
- }
-
- function messagesWaiting(isWaiting) {
- if (marketplaceButton) {
- marketplaceButton.editProperties({
- icon: (isWaiting ? WAITING_ICON : NORMAL_ICON),
- activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE)
- });
- }
- }
-
- function onCanWriteAssetsChanged() {
- var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
- tablet.emitScriptEvent(message);
- }
-
-
- var tabletShouldBeVisibleInSecondaryCamera = false;
- function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) {
- if (visibleInSecondaryCam) {
- // if we're potentially showing the tablet, only do so if it was visible before
- if (!tabletShouldBeVisibleInSecondaryCamera) {
- return;
- }
- } else {
- // if we're hiding the tablet, check to see if it was visible in the first place
- tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera");
- }
-
- Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- }
-
- function openWallet() {
- tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH);
- }
-
- function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) {
- wireEventBridge(true);
- var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID);
- sendToQml({
- method: 'inspectionCertificate_setCertificateId',
- entityId: currentEntityWithContextOverlay,
- certificateId: certificateId
+function onMessageBoxClosed(id, button) {
+ if (id === messageBox && button === CANCEL_BUTTON) {
+ isDownloadBeingCancelled = true;
+ messageBox = null;
+ ui.sendToHtml({
+ type: CLARA_IO_CANCEL_DOWNLOAD
});
}
+}
- function onUsernameChanged() {
- if (onMarketplaceScreen) {
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- }
- }
+function onCanWriteAssetsChanged() {
+ ui.sendToHtml({
+ type: CAN_WRITE_ASSETS,
+ canWriteAssets: Entities.canWriteAssets()
+ });
+}
- var userHasUpdates = false;
- function sendCommerceSettings() {
- tablet.emitScriptEvent(JSON.stringify({
- type: "marketplaces",
- action: "commerceSetting",
- data: {
- commerceMode: Settings.getValue("commerce", true),
- userIsLoggedIn: Account.loggedIn,
- walletNeedsSetup: Wallet.walletStatus === 1,
- metaverseServerURL: Account.metaverseServerURL,
- messagesWaiting: userHasUpdates
- }
- }));
- }
- // BEGIN AVATAR SELECTOR LOGIC
- var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
- var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
- var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
-
- var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
-
- function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
- overlays[key] = this;
- this.key = key;
- this.selected = false;
- this.hovering = false;
- this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
- }
- // Instance methods:
- ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
- Overlays.deleteOverlay(this.activeOverlay);
- delete overlays[this.key];
- };
-
- ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
- Overlays.editOverlay(this.activeOverlay, properties);
- };
-
- function color(selected, hovering) {
- var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
- function scale(component) {
- var delta = 0xFF - component;
- return component;
- }
- return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
- }
- // so we don't have to traverse the overlays to get the last one
- var lastHoveringId = 0;
- ExtendedOverlay.prototype.hover = function (hovering) {
- this.hovering = hovering;
- if (this.key === lastHoveringId) {
- if (hovering) {
- return;
- }
- lastHoveringId = 0;
- }
- this.editOverlay({ color: color(this.selected, hovering) });
- if (hovering) {
- // un-hover the last hovering overlay
- if (lastHoveringId && lastHoveringId !== this.key) {
- ExtendedOverlay.get(lastHoveringId).hover(false);
- }
- lastHoveringId = this.key;
- }
- };
- ExtendedOverlay.prototype.select = function (selected) {
- if (this.selected === selected) {
+var tabletShouldBeVisibleInSecondaryCamera = false;
+function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) {
+ if (visibleInSecondaryCam) {
+ // if we're potentially showing the tablet, only do so if it was visible before
+ if (!tabletShouldBeVisibleInSecondaryCamera) {
return;
}
+ } else {
+ // if we're hiding the tablet, check to see if it was visible in the first place
+ tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera");
+ }
- this.editOverlay({ color: color(selected, this.hovering) });
- this.selected = selected;
- };
- // Class methods:
- var selectedId = false;
- ExtendedOverlay.isSelected = function (id) {
- return selectedId === id;
- };
- ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
- return overlays[key];
- };
- ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
- var key;
- for (key in overlays) {
- if (iterator(ExtendedOverlay.get(key))) {
- return;
- }
+ Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+ Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+ Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+ Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+}
+
+function openWallet() {
+ ui.open(MARKETPLACE_WALLET_QML_PATH);
+}
+
+// Function Name: wireQmlEventBridge()
+//
+// Description:
+// -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
+// disable to event bridge.
+//
+// Relevant Variables:
+// -hasEventBridge: true/false depending on whether we've already connected the event bridge.
+var hasEventBridge = false;
+function wireQmlEventBridge(on) {
+ if (!ui.tablet) {
+ print("Warning in wireQmlEventBridge(): 'tablet' undefined!");
+ return;
+ }
+ if (on) {
+ if (!hasEventBridge) {
+ ui.tablet.fromQml.connect(onQmlMessageReceived);
+ hasEventBridge = true;
}
- };
- ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
- if (lastHoveringId) {
+ } else {
+ if (hasEventBridge) {
+ ui.tablet.fromQml.disconnect(onQmlMessageReceived);
+ hasEventBridge = false;
+ }
+ }
+}
+
+var contextOverlayEntity = "";
+function openInspectionCertificateQML(currentEntityWithContextOverlay) {
+ ui.open(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH);
+ contextOverlayEntity = currentEntityWithContextOverlay;
+}
+
+function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) {
+ var certificateId = itemCertificateId ||
+ (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID);
+ ui.tablet.sendToQml({
+ method: 'inspectionCertificate_setCertificateId',
+ entityId: currentEntityWithContextOverlay,
+ certificateId: certificateId
+ });
+}
+
+function onUsernameChanged() {
+ if (onMarketplaceScreen) {
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ }
+}
+
+var userHasUpdates = false;
+function sendCommerceSettings() {
+ ui.sendToHtml({
+ type: "marketplaces",
+ action: "commerceSetting",
+ data: {
+ commerceMode: Settings.getValue("commerce", true),
+ userIsLoggedIn: Account.loggedIn,
+ walletNeedsSetup: Wallet.walletStatus === 1,
+ metaverseServerURL: Account.metaverseServerURL,
+ messagesWaiting: userHasUpdates
+ }
+ });
+}
+
+// BEGIN AVATAR SELECTOR LOGIC
+var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
+var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
+var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
+
+var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
+
+function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
+ overlays[key] = this;
+ this.key = key;
+ this.selected = false;
+ this.hovering = false;
+ this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+}
+// Instance methods:
+ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
+ Overlays.deleteOverlay(this.activeOverlay);
+ delete overlays[this.key];
+};
+
+ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
+ Overlays.editOverlay(this.activeOverlay, properties);
+};
+
+function color(selected, hovering) {
+ var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
+ function scale(component) {
+ return component;
+ }
+ return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
+}
+// so we don't have to traverse the overlays to get the last one
+var lastHoveringId = 0;
+ExtendedOverlay.prototype.hover = function (hovering) {
+ this.hovering = hovering;
+ if (this.key === lastHoveringId) {
+ if (hovering) {
+ return;
+ }
+ lastHoveringId = 0;
+ }
+ this.editOverlay({ color: color(this.selected, hovering) });
+ if (hovering) {
+ // un-hover the last hovering overlay
+ if (lastHoveringId && lastHoveringId !== this.key) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
- };
+ lastHoveringId = this.key;
+ }
+};
+ExtendedOverlay.prototype.select = function (selected) {
+ if (this.selected === selected) {
+ return;
+ }
- // hit(overlay) on the one overlay intersected by pickRay, if any.
- // noHit() if no ExtendedOverlay was intersected (helps with hover)
- ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
- var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
- if (!pickedOverlay.intersects) {
- if (noHit) {
- return noHit();
- }
+ this.editOverlay({ color: color(selected, this.hovering) });
+ this.selected = selected;
+};
+// Class methods:
+var selectedId = false;
+ExtendedOverlay.isSelected = function (id) {
+ return selectedId === id;
+};
+ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
+ return overlays[key];
+};
+ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
+ var key;
+ for (key in overlays) {
+ if (iterator(ExtendedOverlay.get(key))) {
return;
}
- ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
- if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
- hit(overlay);
- return true;
- }
- });
- };
-
- function addAvatarNode(id) {
- return new ExtendedOverlay(id, "sphere", {
- drawInFront: true,
- solid: true,
- alpha: 0.8,
- color: color(false, false),
- ignoreRayIntersection: false
- });
}
-
- var pingPong = true;
- function updateOverlays() {
- var eye = Camera.position;
- AvatarList.getAvatarIdentifiers().forEach(function (id) {
- if (!id) {
- return; // don't update ourself, or avatars we're not interested in
- }
- var avatar = AvatarList.getAvatar(id);
- if (!avatar) {
- return; // will be deleted below if there had been an overlay.
- }
- var overlay = ExtendedOverlay.get(id);
- if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
- overlay = addAvatarNode(id);
- }
- var target = avatar.position;
- var distance = Vec3.distance(target, eye);
- var offset = 0.2;
- var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
- var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
- if (headIndex > 0) {
- offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
- }
-
- // move a bit in front, towards the camera
- target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
-
- // now bump it up a bit
- target.y = target.y + offset;
-
- overlay.ping = pingPong;
- overlay.editOverlay({
- color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
- position: target,
- dimensions: 0.032 * distance
- });
- });
- pingPong = !pingPong;
- ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
- if (overlay.ping === pingPong) {
- overlay.deleteOverlay();
- }
- });
- }
- function removeOverlays() {
- selectedId = false;
- lastHoveringId = 0;
- ExtendedOverlay.some(function (overlay) {
- overlay.deleteOverlay();
- });
+};
+ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
+ if (lastHoveringId) {
+ ExtendedOverlay.get(lastHoveringId).hover(false);
}
+};
- //
- // Clicks.
- //
- function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
- if (selectedId === id) {
- var message = {
- method: 'updateSelectedRecipientUsername',
- userName: username === "" ? "unknown username" : username
- };
- sendToQml(message);
+// hit(overlay) on the one overlay intersected by pickRay, if any.
+// noHit() if no ExtendedOverlay was intersected (helps with hover)
+ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
+ var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
+ if (!pickedOverlay.intersects) {
+ if (noHit) {
+ return noHit();
}
+ return;
}
- function handleClick(pickRay) {
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- var nextSelectedStatus = !overlay.selected;
- var avatarId = overlay.key;
- selectedId = nextSelectedStatus ? avatarId : false;
- if (nextSelectedStatus) {
- Users.requestUsernameFromID(avatarId);
- }
- var message = {
- method: 'selectRecipient',
- id: avatarId,
- isSelected: nextSelectedStatus,
- displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
- userName: ''
- };
- sendToQml(message);
-
- ExtendedOverlay.some(function (overlay) {
- var id = overlay.key;
- var selected = ExtendedOverlay.isSelected(id);
- overlay.select(selected);
- });
-
+ ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
+ if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
+ hit(overlay);
return true;
- });
- }
- function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
- if (!mousePressEvent.isLeftButton) {
- return;
}
- handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+ });
+};
+
+function addAvatarNode(id) {
+ return new ExtendedOverlay(id, "sphere", {
+ drawInFront: true,
+ solid: true,
+ alpha: 0.8,
+ color: color(false, false),
+ ignoreRayIntersection: false
+ });
+}
+
+var pingPong = true;
+function updateOverlays() {
+ var eye = Camera.position;
+ AvatarList.getAvatarIdentifiers().forEach(function (id) {
+ if (!id) {
+ return; // don't update ourself, or avatars we're not interested in
+ }
+ var avatar = AvatarList.getAvatar(id);
+ if (!avatar) {
+ return; // will be deleted below if there had been an overlay.
+ }
+ var overlay = ExtendedOverlay.get(id);
+ if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
+ overlay = addAvatarNode(id);
+ }
+ var target = avatar.position;
+ var distance = Vec3.distance(target, eye);
+ var offset = 0.2;
+ var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
+ var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
+ if (headIndex > 0) {
+ offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
+ }
+
+ // move a bit in front, towards the camera
+ target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
+
+ // now bump it up a bit
+ target.y = target.y + offset;
+
+ overlay.ping = pingPong;
+ overlay.editOverlay({
+ color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
+ position: target,
+ dimensions: 0.032 * distance
+ });
+ });
+ pingPong = !pingPong;
+ ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
+ if (overlay.ping === pingPong) {
+ overlay.deleteOverlay();
+ }
+ });
+}
+function removeOverlays() {
+ selectedId = false;
+ lastHoveringId = 0;
+ ExtendedOverlay.some(function (overlay) {
+ overlay.deleteOverlay();
+ });
+}
+
+//
+// Clicks.
+//
+function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
+ if (selectedId === id) {
+ var message = {
+ method: 'updateSelectedRecipientUsername',
+ userName: username === "" ? "unknown username" : username
+ };
+ ui.tablet.sendToQml(message);
}
- function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- overlay.hover(true);
- }, function () {
+}
+function handleClick(pickRay) {
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ var nextSelectedStatus = !overlay.selected;
+ var avatarId = overlay.key;
+ selectedId = nextSelectedStatus ? avatarId : false;
+ if (nextSelectedStatus) {
+ Users.requestUsernameFromID(avatarId);
+ }
+ var message = {
+ method: 'selectRecipient',
+ id: avatarId,
+ isSelected: nextSelectedStatus,
+ displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
+ userName: ''
+ };
+ ui.tablet.sendToQml(message);
+
+ ExtendedOverlay.some(function (overlay) {
+ var id = overlay.key;
+ var selected = ExtendedOverlay.isSelected(id);
+ overlay.select(selected);
+ });
+
+ return true;
+ });
+}
+function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
+ if (!mousePressEvent.isLeftButton) {
+ return;
+ }
+ handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+}
+function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ overlay.hover(true);
+ }, function () {
+ ExtendedOverlay.unHover();
+ });
+}
+
+// handy global to keep track of which hand is the mouse (if any)
+var currentHandPressed = 0;
+var TRIGGER_CLICK_THRESHOLD = 0.85;
+var TRIGGER_PRESS_THRESHOLD = 0.05;
+
+function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
+ var pickRay;
+ if (HMD.active) {
+ if (currentHandPressed !== 0) {
+ pickRay = controllerComputePickRay(currentHandPressed);
+ } else {
+ // nothing should hover, so
ExtendedOverlay.unHover();
- });
- }
-
- // handy global to keep track of which hand is the mouse (if any)
- var currentHandPressed = 0;
- var TRIGGER_CLICK_THRESHOLD = 0.85;
- var TRIGGER_PRESS_THRESHOLD = 0.05;
-
- function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
- var pickRay;
- if (HMD.active) {
- if (currentHandPressed !== 0) {
- pickRay = controllerComputePickRay(currentHandPressed);
- } else {
- // nothing should hover, so
- ExtendedOverlay.unHover();
- return;
- }
- } else {
- pickRay = Camera.computePickRay(event.x, event.y);
- }
- handleMouseMove(pickRay);
- }
- function handleTriggerPressed(hand, value) {
- // The idea is if you press one trigger, it is the one
- // we will consider the mouse. Even if the other is pressed,
- // we ignore it until this one is no longer pressed.
- var isPressed = value > TRIGGER_PRESS_THRESHOLD;
- if (currentHandPressed === 0) {
- currentHandPressed = isPressed ? hand : 0;
return;
}
- if (currentHandPressed === hand) {
- currentHandPressed = isPressed ? hand : 0;
- return;
- }
- // otherwise, the other hand is still triggered
- // so do nothing.
+ } else {
+ pickRay = Camera.computePickRay(event.x, event.y);
}
-
- // We get mouseMoveEvents from the handControllers, via handControllerPointer.
- // But we don't get mousePressEvents.
- var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
- var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
- function controllerComputePickRay(hand) {
- var controllerPose = getControllerWorldLocation(hand, true);
- if (controllerPose.valid) {
- return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
- }
+ handleMouseMove(pickRay);
+}
+function handleTriggerPressed(hand, value) {
+ // The idea is if you press one trigger, it is the one
+ // we will consider the mouse. Even if the other is pressed,
+ // we ignore it until this one is no longer pressed.
+ var isPressed = value > TRIGGER_PRESS_THRESHOLD;
+ if (currentHandPressed === 0) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
}
- function makeClickHandler(hand) {
- return function (clicked) {
- if (clicked > TRIGGER_CLICK_THRESHOLD) {
- var pickRay = controllerComputePickRay(hand);
- handleClick(pickRay);
- }
- };
+ if (currentHandPressed === hand) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
}
- function makePressHandler(hand) {
- return function (value) {
- handleTriggerPressed(hand, value);
- };
+ // otherwise, the other hand is still triggered
+ // so do nothing.
+}
+
+// We get mouseMoveEvents from the handControllers, via handControllerPointer.
+// But we don't get mousePressEvents.
+var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
+var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
+function controllerComputePickRay(hand) {
+ var controllerPose = getControllerWorldLocation(hand, true);
+ if (controllerPose.valid) {
+ return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
}
- triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
- triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
- triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
- triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
- // END AVATAR SELECTOR LOGIC
-
- var grid = new Grid();
- function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
- // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
- // position in the given direction.
- var CORNERS = [
- { x: 0, y: 0, z: 0 },
- { x: 0, y: 0, z: 1 },
- { x: 0, y: 1, z: 0 },
- { x: 0, y: 1, z: 1 },
- { x: 1, y: 0, z: 0 },
- { x: 1, y: 0, z: 1 },
- { x: 1, y: 1, z: 0 },
- { x: 1, y: 1, z: 1 },
- ];
-
- // Go through all corners and find least (most negative) distance in front of position.
- var distance = 0;
- for (var i = 0, length = CORNERS.length; i < length; i++) {
- var cornerVector =
- Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
- var cornerDistance = Vec3.dot(cornerVector, direction);
- distance = Math.min(cornerDistance, distance);
+}
+function makeClickHandler(hand) {
+ return function (clicked) {
+ if (clicked > TRIGGER_CLICK_THRESHOLD) {
+ var pickRay = controllerComputePickRay(hand);
+ handleClick(pickRay);
}
- position = Vec3.sum(Vec3.multiply(distance, direction), position);
- return position;
- }
-
- var HALF_TREE_SCALE = 16384;
- function getPositionToCreateEntity(extra) {
- var CREATE_DISTANCE = 2;
- var position;
- var delta = extra !== undefined ? extra : 0;
- if (Camera.mode === "entity" || Camera.mode === "independent") {
- position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta));
- } else {
- position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta));
- position.y += 0.5;
- }
-
- if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
- return null;
- }
- return position;
- }
-
- function rezEntity(itemHref, itemType) {
- var isWearable = itemType === "wearable";
- var success = Clipboard.importEntities(itemHref);
- var wearableLocalPosition = null;
- var wearableLocalRotation = null;
- var wearableLocalDimensions = null;
- var wearableDimensions = null;
-
- if (itemType === "contentSet") {
- console.log("Item is a content set; codepath shouldn't go here.");
- return;
- }
-
- if (isWearable) {
- var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
- if (!wearableTransforms) {
- // TODO delete this clause
- wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms");
- }
- var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here?
- if (certPos >= 0) {
- certPos += 15; // length of "certificate_id="
- var certURLEncoded = itemHref.substring(certPos);
- var certB64Encoded = decodeURIComponent(certURLEncoded);
- for (var key in wearableTransforms) {
- if (wearableTransforms.hasOwnProperty(key)) {
- var certificateTransforms = wearableTransforms[key].certificateTransforms;
- if (certificateTransforms) {
- for (var certID in certificateTransforms) {
- if (certificateTransforms.hasOwnProperty(certID) &&
- certID == certB64Encoded) {
- var certificateTransform = certificateTransforms[certID];
- wearableLocalPosition = certificateTransform.localPosition;
- wearableLocalRotation = certificateTransform.localRotation;
- wearableLocalDimensions = certificateTransform.localDimensions;
- wearableDimensions = certificateTransform.dimensions;
- }
- }
- }
- }
- }
- }
- }
-
- if (success) {
- var VERY_LARGE = 10000;
- var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
- var position = Vec3.ZERO;
- if (!isLargeImport) {
- position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
- }
- if (position !== null && position !== undefined) {
- var pastedEntityIDs = Clipboard.pasteEntities(position);
- if (!isLargeImport) {
- // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
- // entities after they're imported so that they're all the correct distance in front of and with geometric mean
- // centered on the avatar/camera direction.
- var deltaPosition = Vec3.ZERO;
- var entityPositions = [];
- var entityParentIDs = [];
-
- var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type;
- var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
- if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) {
- var targetDirection;
- if (Camera.mode === "entity" || Camera.mode === "independent") {
- targetDirection = Camera.orientation;
- } else {
- targetDirection = MyAvatar.orientation;
- }
- targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z);
-
- var targetPosition = getPositionToCreateEntity();
- var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
- var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
- for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
- var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
- "registrationPoint", "rotation", "parentID"]);
- var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
- curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation);
- var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position);
- var distance = Vec3.dot(delta, targetDirection);
- deltaParallel = Math.min(distance, deltaParallel);
- deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
- deltaPerpendicular);
- entityPositions[i] = curLoopEntityProps.position;
- entityParentIDs[i] = curLoopEntityProps.parentID;
- }
- deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
- deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
- }
-
- if (grid.getSnapToGrid()) {
- var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions",
- "registrationPoint"]);
- var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position);
- position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions,
- firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint);
- deltaPosition = Vec3.subtract(position, firstEntityProps.position);
- }
-
- if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
- for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) {
- if (Uuid.isNull(entityParentIDs[editEntityIndex])) {
- Entities.editEntity(pastedEntityIDs[editEntityIndex], {
- position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex])
- });
- }
- }
- }
- }
-
- if (isWearable) {
- // apply the relative offsets saved during checkout
- var offsets = {};
- if (wearableLocalPosition) {
- offsets.localPosition = wearableLocalPosition;
- }
- if (wearableLocalRotation) {
- offsets.localRotation = wearableLocalRotation;
- }
- if (wearableLocalDimensions) {
- offsets.localDimensions = wearableLocalDimensions;
- } else if (wearableDimensions) {
- offsets.dimensions = wearableDimensions;
- }
- // we currently assume a wearable is a single entity
- Entities.editEntity(pastedEntityIDs[0], offsets);
- }
-
- var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position;
-
- Audio.playSound(REZZING_SOUND, {
- volume: 1.0,
- position: rezPosition,
- localOnly: true
- });
-
- } else {
- Window.notifyEditError("Can't import entities: entities would be out of bounds.");
- }
- } else {
- Window.notifyEditError("There was an error importing the entity file.");
- }
- }
-
- var referrerURL; // Used for updating Purchases QML
- var filterText; // Used for updating Purchases QML
- function onMessage(message) {
- if (message === GOTO_DIRECTORY) {
- tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
- } else if (message === QUERY_CAN_WRITE_ASSETS) {
- tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
- } else if (message === WARN_USER_NO_PERMISSIONS) {
- Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
- } else if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
- if (isDownloadBeingCancelled) {
- return;
- }
-
- var text = message.slice(CLARA_IO_STATUS.length);
- if (messageBox === null) {
- messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
- } else {
- Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
- }
- return;
- } else if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
- if (messageBox !== null) {
- Window.closeMessageBox(messageBox);
- messageBox = null;
- }
- return;
- } else if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
- isDownloadBeingCancelled = false;
- } else {
- var parsedJsonMessage = JSON.parse(message);
- if (parsedJsonMessage.type === "CHECKOUT") {
- wireEventBridge(true);
- tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
- sendToQml({
- method: 'updateCheckoutQML',
- params: parsedJsonMessage
- });
- } else if (parsedJsonMessage.type === "REQUEST_SETTING") {
- sendCommerceSettings();
- } else if (parsedJsonMessage.type === "PURCHASES") {
- referrerURL = parsedJsonMessage.referrerURL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- } else if (parsedJsonMessage.type === "LOGIN") {
- openLoginWindow();
- } else if (parsedJsonMessage.type === "WALLET_SETUP") {
- wireEventBridge(true);
- sendToQml({
- method: 'updateWalletReferrer',
- referrer: "marketplace cta"
- });
- openWallet();
- } else if (parsedJsonMessage.type === "MY_ITEMS") {
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- wireEventBridge(true);
- sendToQml({
- method: 'purchases_showMyItems'
- });
- }
- }
- }
-
- function onButtonClicked() {
- if (!tablet) {
- print("Warning in buttonClicked(): 'tablet' undefined!");
- return;
- }
- if (onMarketplaceScreen || onCommerceScreen) {
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- if (HMD.tabletID) {
- Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
- }
- showMarketplace();
- }
- }
-
- // Function Name: sendToQml()
- //
- // Description:
- // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
- // the QML in the format "{method, params}", like json-rpc. See also fromQml().
- function sendToQml(message) {
- tablet.sendToQml(message);
- }
-
- var sendAssetRecipient;
- var sendAssetParticleEffectUpdateTimer;
- var particleEffectTimestamp;
- var sendAssetParticleEffect;
- var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250;
- var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000;
- var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8;
- var SEND_ASSET_PARTICLE_PROPERTIES = {
- accelerationSpread: { x: 0, y: 0, z: 0 },
- alpha: 1,
- alphaFinish: 1,
- alphaSpread: 0,
- alphaStart: 1,
- azimuthFinish: 0,
- azimuthStart: -6,
- color: { red: 255, green: 222, blue: 255 },
- colorFinish: { red: 255, green: 229, blue: 225 },
- colorSpread: { red: 0, green: 0, blue: 0 },
- colorStart: { red: 243, green: 255, blue: 255 },
- emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
- emitDimensions: { x: 0, y: 0, z: 0 },
- emitOrientation: { x: 0, y: 0, z: 0 },
- emitRate: 4,
- emitSpeed: 2.1,
- emitterShouldTrail: true,
- isEmitting: 1,
- lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
- lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1,
- maxParticles: 20,
- name: 'asset-particles',
- particleRadius: 0.2,
- polarFinish: 0,
- polarStart: 0,
- radiusFinish: 0.05,
- radiusSpread: 0,
- radiusStart: 0.2,
- speedSpread: 0,
- textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
- type: 'ParticleEffect'
};
+}
+function makePressHandler(hand) {
+ return function (value) {
+ handleTriggerPressed(hand, value);
+ };
+}
+triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
+triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
+triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
+triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
+// END AVATAR SELECTOR LOGIC
- function updateSendAssetParticleEffect() {
- var timestampNow = Date.now();
- if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) {
- deleteSendAssetParticleEffect();
- return;
- } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) {
- Entities.editEntity(sendAssetParticleEffect, {
- isEmitting: 0
- });
- } else if (sendAssetParticleEffect) {
- var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position;
- var distance = Vec3.distance(recipientPosition, MyAvatar.position);
- var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
- accel.y -= 3.0;
- var life = Math.sqrt(2 * distance / Vec3.length(accel));
- Entities.editEntity(sendAssetParticleEffect, {
- emitAcceleration: accel,
- lifespan: life
+var grid = new Grid();
+function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
+ // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
+ // position in the given direction.
+ var CORNERS = [
+ { x: 0, y: 0, z: 0 },
+ { x: 0, y: 0, z: 1 },
+ { x: 0, y: 1, z: 0 },
+ { x: 0, y: 1, z: 1 },
+ { x: 1, y: 0, z: 0 },
+ { x: 1, y: 0, z: 1 },
+ { x: 1, y: 1, z: 0 },
+ { x: 1, y: 1, z: 1 }
+ ];
+
+ // Go through all corners and find least (most negative) distance in front of position.
+ var distance = 0;
+ for (var i = 0, length = CORNERS.length; i < length; i++) {
+ var cornerVector =
+ Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
+ var cornerDistance = Vec3.dot(cornerVector, direction);
+ distance = Math.min(cornerDistance, distance);
+ }
+ position = Vec3.sum(Vec3.multiply(distance, direction), position);
+ return position;
+}
+
+var HALF_TREE_SCALE = 16384;
+function getPositionToCreateEntity(extra) {
+ var CREATE_DISTANCE = 2;
+ var position;
+ var delta = extra !== undefined ? extra : 0;
+ if (Camera.mode === "entity" || Camera.mode === "independent") {
+ position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta));
+ } else {
+ position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta));
+ position.y += 0.5;
+ }
+
+ if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
+ return null;
+ }
+ return position;
+}
+
+function rezEntity(itemHref, itemType) {
+ var isWearable = itemType === "wearable";
+ var success = Clipboard.importEntities(itemHref);
+ var wearableLocalPosition = null;
+ var wearableLocalRotation = null;
+ var wearableLocalDimensions = null;
+ var wearableDimensions = null;
+
+ if (itemType === "contentSet") {
+ console.log("Item is a content set; codepath shouldn't go here.");
+ return;
+ }
+
+ if (isWearable) {
+ var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
+ if (!wearableTransforms) {
+ // TODO delete this clause
+ wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms");
+ }
+ var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here?
+ if (certPos >= 0) {
+ certPos += 15; // length of "certificate_id="
+ var certURLEncoded = itemHref.substring(certPos);
+ var certB64Encoded = decodeURIComponent(certURLEncoded);
+ for (var key in wearableTransforms) {
+ if (wearableTransforms.hasOwnProperty(key)) {
+ var certificateTransforms = wearableTransforms[key].certificateTransforms;
+ if (certificateTransforms) {
+ for (var certID in certificateTransforms) {
+ if (certificateTransforms.hasOwnProperty(certID) &&
+ certID == certB64Encoded) {
+ var certificateTransform = certificateTransforms[certID];
+ wearableLocalPosition = certificateTransform.localPosition;
+ wearableLocalRotation = certificateTransform.localRotation;
+ wearableLocalDimensions = certificateTransform.localDimensions;
+ wearableDimensions = certificateTransform.dimensions;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (success) {
+ var VERY_LARGE = 10000;
+ var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
+ var position = Vec3.ZERO;
+ if (!isLargeImport) {
+ position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
+ }
+ if (position !== null && position !== undefined) {
+ var pastedEntityIDs = Clipboard.pasteEntities(position);
+ if (!isLargeImport) {
+ // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
+ // entities after they're imported so that they're all the correct distance in front of and with geometric mean
+ // centered on the avatar/camera direction.
+ var deltaPosition = Vec3.ZERO;
+ var entityPositions = [];
+ var entityParentIDs = [];
+
+ var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type;
+ var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
+ if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) {
+ var targetDirection;
+ if (Camera.mode === "entity" || Camera.mode === "independent") {
+ targetDirection = Camera.orientation;
+ } else {
+ targetDirection = MyAvatar.orientation;
+ }
+ targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z);
+
+ var targetPosition = getPositionToCreateEntity();
+ var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
+ var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
+ for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
+ var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
+ "registrationPoint", "rotation", "parentID"]);
+ var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
+ curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation);
+ var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position);
+ var distance = Vec3.dot(delta, targetDirection);
+ deltaParallel = Math.min(distance, deltaParallel);
+ deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
+ deltaPerpendicular);
+ entityPositions[i] = curLoopEntityProps.position;
+ entityParentIDs[i] = curLoopEntityProps.parentID;
+ }
+ deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
+ deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
+ }
+
+ if (grid.getSnapToGrid()) {
+ var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions",
+ "registrationPoint"]);
+ var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position);
+ position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions,
+ firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint);
+ deltaPosition = Vec3.subtract(position, firstEntityProps.position);
+ }
+
+ if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
+ for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) {
+ if (Uuid.isNull(entityParentIDs[editEntityIndex])) {
+ Entities.editEntity(pastedEntityIDs[editEntityIndex], {
+ position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex])
+ });
+ }
+ }
+ }
+ }
+
+ if (isWearable) {
+ // apply the relative offsets saved during checkout
+ var offsets = {};
+ if (wearableLocalPosition) {
+ offsets.localPosition = wearableLocalPosition;
+ }
+ if (wearableLocalRotation) {
+ offsets.localRotation = wearableLocalRotation;
+ }
+ if (wearableLocalDimensions) {
+ offsets.localDimensions = wearableLocalDimensions;
+ } else if (wearableDimensions) {
+ offsets.dimensions = wearableDimensions;
+ }
+ // we currently assume a wearable is a single entity
+ Entities.editEntity(pastedEntityIDs[0], offsets);
+ }
+
+ var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position;
+
+ Audio.playSound(REZZING_SOUND, {
+ volume: 1.0,
+ position: rezPosition,
+ localOnly: true
});
+
+ } else {
+ Window.notifyEditError("Can't import entities: entities would be out of bounds.");
}
+ } else {
+ Window.notifyEditError("There was an error importing the entity file.");
}
+}
- function deleteSendAssetParticleEffect() {
- if (sendAssetParticleEffectUpdateTimer) {
- Script.clearInterval(sendAssetParticleEffectUpdateTimer);
- sendAssetParticleEffectUpdateTimer = null;
- }
- if (sendAssetParticleEffect) {
- sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect);
- }
- sendAssetRecipient = null;
- }
-
- var savedDisablePreviewOptionLocked = false;
- var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");;
- function maybeEnableHMDPreview() {
- // Set a small timeout to prevent sensitive data from being shown during
- // UI fade
- Script.setTimeout(function () {
- setTabletVisibleInSecondaryCamera(true);
- DesktopPreviewProvider.setPreviewDisabledReason("USER");
- Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption);
- savedDisablePreviewOptionLocked = false;
- }, 150);
- }
-
- // Function Name: fromQml()
- //
- // Description:
- // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML
- // in the format "{method, params}", like json-rpc.
- function fromQml(message) {
- switch (message.method) {
- case 'purchases_openWallet':
- case 'checkout_openWallet':
- case 'checkout_setUpClicked':
- openWallet();
- break;
- case 'purchases_walletNotSetUp':
- wireEventBridge(true);
- sendToQml({
- method: 'updateWalletReferrer',
- referrer: "purchases"
- });
- openWallet();
- break;
- case 'checkout_walletNotSetUp':
- wireEventBridge(true);
- sendToQml({
- method: 'updateWalletReferrer',
- referrer: message.referrer === "itemPage" ? message.itemId : message.referrer
- });
- openWallet();
- break;
- case 'checkout_cancelClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL);
- // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack.
- // I don't think this is trivial to do since we also want to inject some JS into the DOM.
- //tablet.popFromStack();
- break;
- case 'header_goToPurchases':
- case 'checkout_goToPurchases':
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = message.filterText;
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- break;
- case 'checkout_itemLinkClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'checkout_continueShopping':
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- //tablet.popFromStack();
- break;
- case 'purchases_itemInfoClicked':
- var itemId = message.itemId;
- if (itemId && itemId !== "") {
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL);
- }
- break;
- case 'checkout_rezClicked':
- case 'purchases_rezClicked':
- rezEntity(message.itemHref, message.itemType);
- break;
- case 'header_marketplaceImageClicked':
- case 'purchases_backClicked':
- tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'purchases_goToMarketplaceClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'updateItemClicked':
- tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition,
- MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'giftAsset':
-
- break;
- case 'passphrasePopup_cancelClicked':
- case 'needsLogIn_cancelClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'needsLogIn_loginClicked':
- openLoginWindow();
- break;
- case 'disableHmdPreview':
- if (!savedDisablePreviewOption) {
- savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");
- savedDisablePreviewOptionLocked = true;
- }
-
- if (!savedDisablePreviewOption) {
- DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN");
- Menu.setIsOptionChecked("Disable Preview", true);
- setTabletVisibleInSecondaryCamera(false);
- }
- break;
- case 'maybeEnableHmdPreview':
- maybeEnableHMDPreview();
- break;
- case 'purchases_openGoTo':
- tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml");
- break;
- case 'purchases_itemCertificateClicked':
- setCertificateInfo("", message.itemCertificateId);
- break;
- case 'inspectionCertificate_closeClicked':
- tablet.gotoHomeScreen();
- break;
- case 'inspectionCertificate_requestOwnershipVerification':
- ContextOverlay.requestOwnershipVerification(message.entity);
- break;
- case 'inspectionCertificate_showInMarketplaceClicked':
- tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'header_myItemsClicked':
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- wireEventBridge(true);
- sendToQml({
- method: 'purchases_showMyItems'
- });
- break;
- case 'refreshConnections':
- // Guard to prevent this code from being executed while sending money --
- // we only want to execute this while sending non-HFC gifts
- if (!onWalletScreen) {
- print('Refreshing Connections...');
- getConnectionData(false);
- }
- break;
- case 'enable_ChooseRecipientNearbyMode':
- // Guard to prevent this code from being executed while sending money --
- // we only want to execute this while sending non-HFC gifts
- if (!onWalletScreen) {
- if (!isUpdateOverlaysWired) {
- Script.update.connect(updateOverlays);
- isUpdateOverlaysWired = true;
- }
- }
- break;
- case 'disable_ChooseRecipientNearbyMode':
- // Guard to prevent this code from being executed while sending money --
- // we only want to execute this while sending non-HFC gifts
- if (!onWalletScreen) {
- if (isUpdateOverlaysWired) {
- Script.update.disconnect(updateOverlays);
- isUpdateOverlaysWired = false;
- }
- removeOverlays();
- }
- break;
- case 'wallet_availableUpdatesReceived':
- case 'purchases_availableUpdatesReceived':
- userHasUpdates = message.numUpdates > 0;
- messagesWaiting(userHasUpdates);
- break;
- case 'purchases_updateWearables':
- var currentlyWornWearables = [];
- var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case)
-
- var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS);
-
- for (var i = 0; i < nearbyEntities.length; i++) {
- var currentProperties = Entities.getEntityProperties(nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']);
- if (currentProperties.parentID === MyAvatar.sessionUUID) {
- currentlyWornWearables.push({
- entityID: nearbyEntities[i],
- entityCertID: currentProperties.certificateID,
- entityEdition: currentProperties.editionNumber
- });
- }
- }
-
- sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables });
- break;
- case 'sendAsset_sendPublicly':
- if (message.assetName !== "") {
- deleteSendAssetParticleEffect();
- sendAssetRecipient = message.recipient;
- var amount = message.amount;
- var props = SEND_ASSET_PARTICLE_PROPERTIES;
- props.parentID = MyAvatar.sessionUUID;
- props.position = MyAvatar.position;
- props.position.y += 0.2;
- if (message.effectImage) {
- props.textures = message.effectImage;
- }
- sendAssetParticleEffect = Entities.addEntity(props, true);
- particleEffectTimestamp = Date.now();
- updateSendAssetParticleEffect();
- sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE);
- }
- break;
- case 'http.request':
- // Handled elsewhere, don't log.
- break;
- case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about?
- break;
- default:
- print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
- }
- }
-
- // Function Name: wireEventBridge()
- //
- // Description:
- // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
- // disable to event bridge.
- //
- // Relevant Variables:
- // -hasEventBridge: true/false depending on whether we've already connected the event bridge.
- var hasEventBridge = false;
- function wireEventBridge(on) {
- if (!tablet) {
- print("Warning in wireEventBridge(): 'tablet' undefined!");
+var referrerURL; // Used for updating Purchases QML
+var filterText; // Used for updating Purchases QML
+function onWebEventReceived(message) {
+ message = JSON.parse(message);
+ if (message.type === GOTO_DIRECTORY) {
+ ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
+ } else if (message.type === QUERY_CAN_WRITE_ASSETS) {
+ ui.sendToHtml(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
+ } else if (message.type === WARN_USER_NO_PERMISSIONS) {
+ Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
+ } else if (message.type === CLARA_IO_STATUS) {
+ if (isDownloadBeingCancelled) {
return;
}
- if (on) {
- if (!hasEventBridge) {
- tablet.fromQml.connect(fromQml);
- hasEventBridge = true;
- }
+
+ var text = message.status;
+ if (messageBox === null) {
+ messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
} else {
- if (hasEventBridge) {
- tablet.fromQml.disconnect(fromQml);
- hasEventBridge = false;
- }
+ Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
}
- }
-
- // Function Name: onTabletScreenChanged()
- //
- // Description:
- // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
- // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
- var onWalletScreen = false;
- var onCommerceScreen = false;
- function onTabletScreenChanged(type, url) {
- onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
- var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
- var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH
- || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
-
- if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen
- maybeEnableHMDPreview();
+ return;
+ } else if (message.type === CLARA_IO_DOWNLOAD) {
+ if (messageBox !== null) {
+ Window.closeMessageBox(messageBox);
+ messageBox = null;
}
-
- onCommerceScreen = onCommerceScreenNow;
- onWalletScreen = onWalletScreenNow;
- wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen);
-
- if (url === MARKETPLACE_PURCHASES_QML_PATH) {
- sendToQml({
- method: 'updatePurchases',
- referrerURL: referrerURL,
- filterText: filterText
- });
- }
-
- // for toolbar mode: change button to active when window is first openend, false otherwise.
- if (marketplaceButton) {
- marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen });
- }
- if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
- ContextOverlay.isInMarketplaceInspectionMode = true;
- } else {
- ContextOverlay.isInMarketplaceInspectionMode = false;
- }
-
- if (onCommerceScreen) {
- if (!isWired) {
- Users.usernameFromIDReply.connect(usernameFromIDReply);
- Controller.mousePressEvent.connect(handleMouseEvent);
- Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
- triggerMapping.enable();
- triggerPressMapping.enable();
- }
- isWired = true;
- Wallet.refreshWalletStatus();
- } else {
- off();
- sendToQml({
- method: 'inspectionCertificate_resetCert'
- });
- }
- }
-
- //
- // Manage the connection between the button and the window.
- //
- var marketplaceButton;
- var buttonName = "MARKET";
- var tablet = null;
- var NORMAL_ICON = "icons/tablet-icons/market-i.svg";
- var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg";
- var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg";
- var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg";
- function startup() {
- tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- marketplaceButton = tablet.addButton({
- icon: NORMAL_ICON,
- activeIcon: NORMAL_ACTIVE,
- text: buttonName,
- sortOrder: 9
+ return;
+ } else if (message.type === CLARA_IO_CANCELLED_DOWNLOAD) {
+ isDownloadBeingCancelled = false;
+ } else if (message.type === "CHECKOUT") {
+ wireQmlEventBridge(true);
+ ui.open(MARKETPLACE_CHECKOUT_QML_PATH);
+ ui.tablet.sendToQml({
+ method: 'updateCheckoutQML',
+ params: message
+ });
+ } else if (message.type === "REQUEST_SETTING") {
+ sendCommerceSettings();
+ } else if (message.type === "PURCHASES") {
+ referrerURL = message.referrerURL;
+ filterText = "";
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ } else if (message.type === "LOGIN") {
+ openLoginWindow();
+ } else if (message.type === "WALLET_SETUP") {
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'updateWalletReferrer',
+ referrer: "marketplace cta"
+ });
+ openWallet();
+ } else if (message.type === "MY_ITEMS") {
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = "";
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'purchases_showMyItems'
});
-
- ContextOverlay.contextOverlayClicked.connect(setCertificateInfo);
- Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
- GlobalServices.myUsernameChanged.connect(onUsernameChanged);
- marketplaceButton.clicked.connect(onButtonClicked);
- tablet.screenChanged.connect(onTabletScreenChanged);
- tablet.webEventReceived.connect(onMessage);
- Wallet.walletStatusChanged.connect(sendCommerceSettings);
- Window.messageBoxClosed.connect(onMessageBoxClosed);
-
- Wallet.refreshWalletStatus();
}
- var isWired = false;
- var isUpdateOverlaysWired = false;
- function off() {
- if (isWired) {
- Users.usernameFromIDReply.disconnect(usernameFromIDReply);
- Controller.mousePressEvent.disconnect(handleMouseEvent);
- Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
- triggerMapping.disable();
- triggerPressMapping.disable();
+}
+var sendAssetRecipient;
+var sendAssetParticleEffectUpdateTimer;
+var particleEffectTimestamp;
+var sendAssetParticleEffect;
+var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250;
+var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000;
+var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8;
+var SEND_ASSET_PARTICLE_PROPERTIES = {
+ accelerationSpread: { x: 0, y: 0, z: 0 },
+ alpha: 1,
+ alphaFinish: 1,
+ alphaSpread: 0,
+ alphaStart: 1,
+ azimuthFinish: 0,
+ azimuthStart: -6,
+ color: { red: 255, green: 222, blue: 255 },
+ colorFinish: { red: 255, green: 229, blue: 225 },
+ colorSpread: { red: 0, green: 0, blue: 0 },
+ colorStart: { red: 243, green: 255, blue: 255 },
+ emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
+ emitDimensions: { x: 0, y: 0, z: 0 },
+ emitOrientation: { x: 0, y: 0, z: 0 },
+ emitRate: 4,
+ emitSpeed: 2.1,
+ emitterShouldTrail: true,
+ isEmitting: 1,
+ lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
+ lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1,
+ maxParticles: 20,
+ name: 'asset-particles',
+ particleRadius: 0.2,
+ polarFinish: 0,
+ polarStart: 0,
+ radiusFinish: 0.05,
+ radiusSpread: 0,
+ radiusStart: 0.2,
+ speedSpread: 0,
+ textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
+ type: 'ParticleEffect'
+};
- isWired = false;
- }
- if (isUpdateOverlaysWired) {
- Script.update.disconnect(updateOverlays);
- isUpdateOverlaysWired = false;
- }
- removeOverlays();
- }
- function shutdown() {
- maybeEnableHMDPreview();
+function updateSendAssetParticleEffect() {
+ var timestampNow = Date.now();
+ if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) {
deleteSendAssetParticleEffect();
+ return;
+ } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) {
+ Entities.editEntity(sendAssetParticleEffect, {
+ isEmitting: 0
+ });
+ } else if (sendAssetParticleEffect) {
+ var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position;
+ var distance = Vec3.distance(recipientPosition, MyAvatar.position);
+ var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
+ accel.y -= 3.0;
+ var life = Math.sqrt(2 * distance / Vec3.length(accel));
+ Entities.editEntity(sendAssetParticleEffect, {
+ emitAcceleration: accel,
+ lifespan: life
+ });
+ }
+}
- ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo);
- Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
- GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
- marketplaceButton.clicked.disconnect(onButtonClicked);
- tablet.removeButton(marketplaceButton);
- tablet.webEventReceived.disconnect(onMessage);
- Wallet.walletStatusChanged.disconnect(sendCommerceSettings);
- Window.messageBoxClosed.disconnect(onMessageBoxClosed);
+function deleteSendAssetParticleEffect() {
+ if (sendAssetParticleEffectUpdateTimer) {
+ Script.clearInterval(sendAssetParticleEffectUpdateTimer);
+ sendAssetParticleEffectUpdateTimer = null;
+ }
+ if (sendAssetParticleEffect) {
+ sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect);
+ }
+ sendAssetRecipient = null;
+}
- if (tablet) {
- tablet.screenChanged.disconnect(onTabletScreenChanged);
- if (onMarketplaceScreen) {
- tablet.gotoHomeScreen();
+var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");
+var UI_FADE_TIMEOUT_MS = 150;
+function maybeEnableHMDPreview() {
+ // Set a small timeout to prevent sensitive data from being shown during UI fade
+ Script.setTimeout(function () {
+ setTabletVisibleInSecondaryCamera(true);
+ DesktopPreviewProvider.setPreviewDisabledReason("USER");
+ Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption);
+ }, UI_FADE_TIMEOUT_MS);
+}
+
+var resourceObjectsInTest = [];
+function signalNewResourceObjectInTest(resourceObject) {
+ ui.tablet.sendToQml({
+ method: "newResourceObjectInTest",
+ resourceObject: resourceObject });
+}
+
+var onQmlMessageReceived = function onQmlMessageReceived(message) {
+ if (message.messageSrc === "HTML") {
+ return;
+ }
+ switch (message.method) {
+ case 'purchases_openWallet':
+ case 'checkout_openWallet':
+ case 'checkout_setUpClicked':
+ openWallet();
+ break;
+ case 'purchases_walletNotSetUp':
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'updateWalletReferrer',
+ referrer: "purchases"
+ });
+ openWallet();
+ break;
+ case 'checkout_walletNotSetUp':
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'updateWalletReferrer',
+ referrer: message.referrer === "itemPage" ? message.itemId : message.referrer
+ });
+ openWallet();
+ break;
+ case 'checkout_cancelClicked':
+ ui.open(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'header_goToPurchases':
+ case 'checkout_goToPurchases':
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = message.filterText;
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ break;
+ case 'checkout_itemLinkClicked':
+ ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'checkout_continueShopping':
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'purchases_itemInfoClicked':
+ var itemId = message.itemId;
+ if (itemId && itemId !== "") {
+ ui.open(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL);
+ }
+ break;
+ case 'checkout_rezClicked':
+ case 'purchases_rezClicked':
+ case 'tester_rezClicked':
+ rezEntity(message.itemHref, message.itemType);
+ break;
+ case 'tester_newResourceObject':
+ var resourceObject = message.resourceObject;
+ resourceObjectsInTest[resourceObject.id] = resourceObject;
+ signalNewResourceObjectInTest(resourceObject);
+ break;
+ case 'tester_updateResourceObjectAssetType':
+ resourceObjectsInTest[message.objectId].assetType = message.assetType;
+ break;
+ case 'tester_deleteResourceObject':
+ delete resourceObjectsInTest[message.objectId];
+ break;
+ case 'header_marketplaceImageClicked':
+ case 'purchases_backClicked':
+ ui.open(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'purchases_goToMarketplaceClicked':
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'updateItemClicked':
+ ui.open(message.upgradeUrl + "?edition=" + message.itemEdition,
+ MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'giftAsset':
+
+ break;
+ case 'passphrasePopup_cancelClicked':
+ case 'needsLogIn_cancelClicked':
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'needsLogIn_loginClicked':
+ openLoginWindow();
+ break;
+ case 'disableHmdPreview':
+ if (!savedDisablePreviewOption) {
+ DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN");
+ Menu.setIsOptionChecked("Disable Preview", true);
+ setTabletVisibleInSecondaryCamera(false);
+ }
+ break;
+ case 'maybeEnableHmdPreview':
+ maybeEnableHMDPreview();
+ break;
+ case 'purchases_openGoTo':
+ ui.open("hifi/tablet/TabletAddressDialog.qml");
+ break;
+ case 'purchases_itemCertificateClicked':
+ contextOverlayEntity = "";
+ setCertificateInfo(contextOverlayEntity, message.itemCertificateId);
+ break;
+ case 'inspectionCertificate_closeClicked':
+ ui.close();
+ break;
+ case 'inspectionCertificate_requestOwnershipVerification':
+ ContextOverlay.requestOwnershipVerification(message.entity);
+ break;
+ case 'inspectionCertificate_showInMarketplaceClicked':
+ ui.open(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'header_myItemsClicked':
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = "";
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'purchases_showMyItems'
+ });
+ break;
+ case 'refreshConnections':
+ // Guard to prevent this code from being executed while sending money --
+ // we only want to execute this while sending non-HFC gifts
+ if (!onWalletScreen) {
+ print('Refreshing Connections...');
+ getConnectionData(false);
+ }
+ break;
+ case 'enable_ChooseRecipientNearbyMode':
+ // Guard to prevent this code from being executed while sending money --
+ // we only want to execute this while sending non-HFC gifts
+ if (!onWalletScreen) {
+ if (!isUpdateOverlaysWired) {
+ Script.update.connect(updateOverlays);
+ isUpdateOverlaysWired = true;
+ }
+ }
+ break;
+ case 'disable_ChooseRecipientNearbyMode':
+ // Guard to prevent this code from being executed while sending money --
+ // we only want to execute this while sending non-HFC gifts
+ if (!onWalletScreen) {
+ if (isUpdateOverlaysWired) {
+ Script.update.disconnect(updateOverlays);
+ isUpdateOverlaysWired = false;
+ }
+ removeOverlays();
+ }
+ break;
+ case 'wallet_availableUpdatesReceived':
+ case 'purchases_availableUpdatesReceived':
+ userHasUpdates = message.numUpdates > 0;
+ ui.messagesWaiting(userHasUpdates);
+ break;
+ case 'purchases_updateWearables':
+ var currentlyWornWearables = [];
+ var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case)
+
+ var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS);
+
+ for (var i = 0; i < nearbyEntities.length; i++) {
+ var currentProperties = Entities.getEntityProperties(
+ nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']
+ );
+ if (currentProperties.parentID === MyAvatar.sessionUUID) {
+ currentlyWornWearables.push({
+ entityID: nearbyEntities[i],
+ entityCertID: currentProperties.certificateID,
+ entityEdition: currentProperties.editionNumber
+ });
}
}
+ ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables });
+ break;
+ case 'sendAsset_sendPublicly':
+ if (message.assetName !== "") {
+ deleteSendAssetParticleEffect();
+ sendAssetRecipient = message.recipient;
+ var props = SEND_ASSET_PARTICLE_PROPERTIES;
+ props.parentID = MyAvatar.sessionUUID;
+ props.position = MyAvatar.position;
+ props.position.y += 0.2;
+ if (message.effectImage) {
+ props.textures = message.effectImage;
+ }
+ sendAssetParticleEffect = Entities.addEntity(props, true);
+ particleEffectTimestamp = Date.now();
+ updateSendAssetParticleEffect();
+ sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect,
+ SEND_ASSET_PARTICLE_TIMER_UPDATE);
+ }
+ break;
+ case 'http.request':
+ // Handled elsewhere, don't log.
+ break;
+ case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about?
+ break;
+ default:
+ print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
+ }
+};
+
+function pushResourceObjectsInTest() {
+ var maxObjectId = -1;
+ for (var objectId in resourceObjectsInTest) {
+ signalNewResourceObjectInTest(resourceObjectsInTest[objectId]);
+ maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId;
+ }
+ // N.B. Thinking about removing the following sendToQml? Be sure
+ // that the marketplace item tester QML has heard from us, at least
+ // so that it can indicate to the user that all of the resoruce
+ // objects in test have been transmitted to it.
+ ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxObjectId + 1 });
+}
+
+// Function Name: onTabletScreenChanged()
+//
+// Description:
+// -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
+// value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
+
+var onCommerceScreen = false;
+var onInspectionCertificateScreen = false;
+var onMarketplaceItemTesterScreen = false;
+var onMarketplaceScreen = false;
+var onWalletScreen = false;
+var onTabletScreenChanged = function onTabletScreenChanged(type, url) {
+ ui.setCurrentVisibleScreenMetadata(type, url);
+ onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
+ onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1;
+ var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
+ var onCommerceScreenNow = type === "QML" && (
+ url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 ||
+ url === MARKETPLACE_PURCHASES_QML_PATH ||
+ url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
+ var onMarketplaceItemTesterScreenNow = (
+ url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 ||
+ url === MARKETPLACE_ITEM_TESTER_QML_PATH);
+
+ if ((!onWalletScreenNow && onWalletScreen) ||
+ (!onCommerceScreenNow && onCommerceScreen) ||
+ (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen)
+ ) {
+ // exiting wallet, commerce, or marketplace item tester screen
+ maybeEnableHMDPreview();
+ }
+
+ onCommerceScreen = onCommerceScreenNow;
+ onWalletScreen = onWalletScreenNow;
+ onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow;
+
+ wireQmlEventBridge(
+ onMarketplaceScreen ||
+ onCommerceScreen ||
+ onWalletScreen ||
+ onMarketplaceItemTesterScreen);
+
+ if (url === MARKETPLACE_PURCHASES_QML_PATH) {
+ // FIXME? Is there a race condition here in which the event
+ // bridge may not be up yet? If so, Script.setTimeout(..., 750)
+ // may help avoid the condition.
+ ui.tablet.sendToQml({
+ method: 'updatePurchases',
+ referrerURL: referrerURL,
+ filterText: filterText
+ });
+ }
+
+ ui.isOpen = (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen;
+ ui.buttonActive(ui.isOpen);
+
+ if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
+ ContextOverlay.isInMarketplaceInspectionMode = true;
+ } else {
+ ContextOverlay.isInMarketplaceInspectionMode = false;
+ }
+
+ if (onInspectionCertificateScreen) {
+ setCertificateInfo(contextOverlayEntity);
+ }
+
+ if (onCommerceScreen) {
+ if (!isWired) {
+ Users.usernameFromIDReply.connect(usernameFromIDReply);
+ Controller.mousePressEvent.connect(handleMouseEvent);
+ Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+ triggerMapping.enable();
+ triggerPressMapping.enable();
+ }
+ isWired = true;
+ Wallet.refreshWalletStatus();
+ } else {
+ ui.tablet.sendToQml({
+ method: 'inspectionCertificate_resetCert'
+ });
off();
}
- //
- // Run the functions.
- //
- startup();
- Script.scriptEnding.connect(shutdown);
+ if (onMarketplaceItemTesterScreen) {
+ // Why setTimeout? The QML event bridge, wired above, requires a
+ // variable amount of time to come up, in practice less than
+ // 750ms.
+ Script.setTimeout(pushResourceObjectsInTest, 750);
+ }
+ console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type +
+ "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n");
+};
+
+//
+// Manage the connection between the button and the window.
+//
+var BUTTON_NAME = "MARKET";
+var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
+var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
+var ui;
+function startup() {
+ ui = new AppUi({
+ buttonName: BUTTON_NAME,
+ sortOrder: 9,
+ inject: MARKETPLACES_INJECT_SCRIPT_URL,
+ home: MARKETPLACE_URL_INITIAL,
+ onScreenChanged: onTabletScreenChanged,
+ onMessage: onQmlMessageReceived
+ });
+ ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML);
+ Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
+ GlobalServices.myUsernameChanged.connect(onUsernameChanged);
+ ui.tablet.webEventReceived.connect(onWebEventReceived);
+ Wallet.walletStatusChanged.connect(sendCommerceSettings);
+ Window.messageBoxClosed.connect(onMessageBoxClosed);
+
+ Wallet.refreshWalletStatus();
+}
+
+var isWired = false;
+var isUpdateOverlaysWired = false;
+function off() {
+ if (isWired) {
+ Users.usernameFromIDReply.disconnect(usernameFromIDReply);
+ Controller.mousePressEvent.disconnect(handleMouseEvent);
+ Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
+ triggerMapping.disable();
+ triggerPressMapping.disable();
+
+ isWired = false;
+ }
+
+ if (isUpdateOverlaysWired) {
+ Script.update.disconnect(updateOverlays);
+ isUpdateOverlaysWired = false;
+ }
+ removeOverlays();
+}
+function shutdown() {
+ maybeEnableHMDPreview();
+ deleteSendAssetParticleEffect();
+
+ Window.messageBoxClosed.disconnect(onMessageBoxClosed);
+ Wallet.walletStatusChanged.disconnect(sendCommerceSettings);
+ ui.tablet.webEventReceived.disconnect(onWebEventReceived);
+ GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
+ Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
+ ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML);
+
+ off();
+}
+
+//
+// Run the functions.
+//
+startup();
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index ebb45130e5..85898c28fb 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -1,6 +1,9 @@
"use strict";
-/*jslint vars:true, plusplus:true, forin:true*/
-/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/
+/* jslint vars:true, plusplus:true, forin:true */
+/* global Tablet, Settings, Script, AvatarList, Users, Entities,
+ MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account,
+ UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation
+*/
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// pal.js
@@ -20,7 +23,7 @@ var AppUi = Script.require('appUi');
var populateNearbyUserList, color, textures, removeOverlays,
controllerComputePickRay, off,
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
- CHANNEL, getConnectionData, findableByChanged,
+ CHANNEL, getConnectionData,
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
@@ -318,6 +321,10 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
break;
case 'http.request':
break; // Handled by request-service.
+ case 'hideNotificationDot':
+ shouldShowDot = false;
+ ui.messagesWaiting(shouldShowDot);
+ break;
default:
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
}
@@ -361,8 +368,8 @@ function getProfilePicture(username, callback) { // callback(url) if successfull
});
}
var SAFETY_LIMIT = 400;
-function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
- var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&';
+function getAvailableConnections(domain, callback, numResultsPerPage) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
+ var url = METAVERSE_BASE + '/api/v1/users?per_page=' + (numResultsPerPage || SAFETY_LIMIT) + '&';
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {
@@ -713,7 +720,7 @@ function tabletVisibilityChanged() {
if (!ui.tablet.tabletShown && ui.isOpen) {
ui.close();
}
- }
+}
var UPDATE_INTERVAL_MS = 100;
var updateInterval;
@@ -725,10 +732,14 @@ function createUpdateInterval() {
var previousContextOverlay = ContextOverlay.enabled;
var previousRequestsDomainListData = Users.requestsDomainListData;
-function on() {
+function palOpened() {
+ ui.sendMessage({
+ method: 'changeConnectionsDotStatus',
+ shouldShowDot: shouldShowDot
+ });
previousContextOverlay = ContextOverlay.enabled;
- previousRequestsDomainListData = Users.requestsDomainListData
+ previousRequestsDomainListData = Users.requestsDomainListData;
ContextOverlay.enabled = false;
Users.requestsDomainListData = true;
@@ -807,14 +818,98 @@ function avatarSessionChanged(avatarID) {
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] });
}
+function notificationDataProcessPage(data) {
+ return data.data.users;
+}
+
+var shouldShowDot = false;
+var storedOnlineUsersArray = [];
+function notificationPollCallback(connectionsArray) {
+ //
+ // START logic for handling online/offline user changes
+ //
+ var i, j;
+ var newlyOnlineConnectionsArray = [];
+ for (i = 0; i < connectionsArray.length; i++) {
+ var currentUser = connectionsArray[i];
+
+ if (connectionsArray[i].online) {
+ var indexOfStoredOnlineUser = -1;
+ for (j = 0; j < storedOnlineUsersArray.length; j++) {
+ if (currentUser.username === storedOnlineUsersArray[j].username) {
+ indexOfStoredOnlineUser = j;
+ break;
+ }
+ }
+ // If the user record isn't already presesnt inside `storedOnlineUsersArray`...
+ if (indexOfStoredOnlineUser < 0) {
+ storedOnlineUsersArray.push(currentUser);
+ newlyOnlineConnectionsArray.push(currentUser);
+ }
+ } else {
+ var indexOfOfflineUser = -1;
+ for (j = 0; j < storedOnlineUsersArray.length; j++) {
+ if (currentUser.username === storedOnlineUsersArray[j].username) {
+ indexOfOfflineUser = j;
+ break;
+ }
+ }
+ if (indexOfOfflineUser >= 0) {
+ storedOnlineUsersArray.splice(indexOfOfflineUser);
+ }
+ }
+ }
+ // If there's new data, the light should turn on.
+ // If the light is already on and you have connections online, the light should stay on.
+ // In all other cases, the light should turn off or stay off.
+ shouldShowDot = newlyOnlineConnectionsArray.length > 0 || (storedOnlineUsersArray.length > 0 && shouldShowDot);
+ //
+ // END logic for handling online/offline user changes
+ //
+
+ if (!ui.isOpen) {
+ ui.messagesWaiting(shouldShowDot);
+ ui.sendMessage({
+ method: 'changeConnectionsDotStatus',
+ shouldShowDot: shouldShowDot
+ });
+
+ if (newlyOnlineConnectionsArray.length > 0) {
+ var message;
+ if (!ui.notificationInitialCallbackMade) {
+ message = newlyOnlineConnectionsArray.length + " of your connections " +
+ (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!";
+ ui.notificationDisplayBanner(message);
+ } else {
+ for (i = 0; i < newlyOnlineConnectionsArray.length; i++) {
+ message = newlyOnlineConnectionsArray[i].username + " is available in " +
+ newlyOnlineConnectionsArray[i].location.root.name + ". Open PEOPLE to join them!";
+ ui.notificationDisplayBanner(message);
+ }
+ }
+ }
+ }
+}
+
+function isReturnedDataEmpty(data) {
+ var usersArray = data.data.users;
+ return usersArray.length === 0;
+}
+
function startup() {
ui = new AppUi({
buttonName: "PEOPLE",
sortOrder: 7,
home: "hifi/Pal.qml",
- onOpened: on,
+ onOpened: palOpened,
onClosed: off,
- onMessage: fromQml
+ onMessage: fromQml,
+ notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10",
+ notificationPollTimeoutMs: 60000,
+ notificationDataProcessPage: notificationDataProcessPage,
+ notificationPollCallback: notificationPollCallback,
+ notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
+ notificationPollCaresAboutSince: false
});
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index 37270f896e..3d744b3bd2 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -7,28 +7,19 @@
// Distributed under the Apache License, Version 2.0
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */
+/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle,
+ OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function () { // BEGIN LOCAL_SCOPE
Script.include("/~/system/libraries/accountUtils.js");
+var AppUi = Script.require('appUi');
var SNAPSHOT_DELAY = 500; // 500ms
var FINISH_SOUND_DELAY = 350;
var resetOverlays;
var reticleVisible;
-var buttonName = "SNAP";
-var buttonConnected = false;
-
-var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
-var button = tablet.addButton({
- icon: "icons/tablet-icons/snap-i.svg",
- activeIcon: "icons/tablet-icons/snap-a.svg",
- text: buttonName,
- sortOrder: 5
-});
-
var snapshotOptions = {};
var imageData = [];
var storyIDsToMaybeDelete = [];
@@ -52,8 +43,6 @@ try {
print('Failed to resolve request api, error: ' + err);
}
-
-
function removeFromStoryIDsToMaybeDelete(story_id) {
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
@@ -73,33 +62,32 @@ function onMessage(message) {
// 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the
// same time, show the user all of them, and have the user deselect any that they do not want to share.
// So we'll ultimately be receiving a set of objects, perhaps with different post processing for each.
- message = JSON.parse(message);
if (message.type !== "snapshot") {
return;
}
switch (message.action) {
case 'ready': // DOM is ready and page has loaded
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "captureSettings",
setting: Settings.getValue("alsoTakeAnimatedSnapshot", true)
- }));
+ });
if (Snapshot.getSnapshotsLocation() !== "") {
isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "showPreviousImages",
options: snapshotOptions,
image_data: imageData,
canShare: canShare
- }));
+ });
});
} else {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "showSetupInstructions"
- }));
+ });
Settings.setValue("previousStillSnapPath", "");
Settings.setValue("previousStillSnapStoryID", "");
Settings.setValue("previousStillSnapBlastingDisabled", false);
@@ -124,7 +112,7 @@ function onMessage(message) {
|| (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
} else {
- tablet.loadQMLOnTop("hifi/tablet/TabletGeneralPreferences.qml");
+ ui.openNewAppOnTop("hifi/tablet/TabletGeneralPreferences.qml");
}
break;
case 'captureStillAndGif':
@@ -284,7 +272,6 @@ var POLAROID_RATE_LIMIT_MS = 1000;
var polaroidPrintingIsRateLimited = false;
function printToPolaroid(image_url) {
-
// Rate-limit printing
if (polaroidPrintingIsRateLimited) {
return;
@@ -376,19 +363,6 @@ function fillImageDataFromPrevious() {
}
}
-var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html");
-var isInSnapshotReview = false;
-function onButtonClicked() {
- if (isInSnapshotReview){
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- fillImageDataFromPrevious();
- tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL);
- HMD.openTablet();
- }
-}
-
function snapshotUploaded(isError, reply) {
if (!isError) {
var replyJson = JSON.parse(reply),
@@ -409,12 +383,12 @@ function snapshotUploaded(isError, reply) {
}
if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) {
print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "snapshotUploadComplete",
story_id: storyID,
image_url: imageURL,
- }));
+ });
if (isGif) {
Settings.setValue("previousAnimatedSnapStoryID", storyID);
} else {
@@ -429,10 +403,10 @@ function snapshotUploaded(isError, reply) {
}
var href, snapshotDomainID;
function takeSnapshot() {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "clearPreviousImages"
- }));
+ });
Settings.setValue("previousStillSnapPath", "");
Settings.setValue("previousStillSnapStoryID", "");
Settings.setValue("previousStillSnapBlastingDisabled", false);
@@ -471,10 +445,6 @@ function takeSnapshot() {
} else {
Window.stillSnapshotTaken.connect(stillSnapshotTaken);
}
- if (buttonConnected) {
- button.clicked.disconnect(onButtonClicked);
- buttonConnected = false;
- }
// hide overlays if they are on
if (resetOverlays) {
@@ -538,10 +508,6 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
Menu.setIsOptionChecked("Show Overlays", true);
}
Window.stillSnapshotTaken.disconnect(stillSnapshotTaken);
- if (!buttonConnected) {
- button.clicked.connect(onButtonClicked);
- buttonConnected = true;
- }
// A Snapshot Review dialog might be left open indefinitely after taking the picture,
// during which time the user may have moved. So stash that info in the dialog so that
@@ -559,12 +525,12 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
isLoggedIn: isLoggedIn
};
imageData = [{ localPath: pathStillSnapshot, href: href }];
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "addImages",
options: snapshotOptions,
image_data: imageData
- }));
+ });
});
}
@@ -572,10 +538,10 @@ function snapshotDirChanged(snapshotPath) {
Window.browseDirChanged.disconnect(snapshotDirChanged);
if (snapshotPath !== "") { // not cancelled
Snapshot.setSnapshotsLocation(snapshotPath);
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "snapshotLocationChosen"
- }));
+ });
}
}
@@ -603,22 +569,18 @@ function processingGifStarted(pathStillSnapshot) {
isLoggedIn: isLoggedIn
};
imageData = [{ localPath: pathStillSnapshot, href: href }];
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "addImages",
options: snapshotOptions,
image_data: imageData
- }));
+ });
});
}
function processingGifCompleted(pathAnimatedSnapshot) {
isLoggedIn = Account.isLoggedIn();
Window.processingGifCompleted.disconnect(processingGifCompleted);
- if (!buttonConnected) {
- button.clicked.connect(onButtonClicked);
- buttonConnected = true;
- }
Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot);
@@ -631,12 +593,12 @@ function processingGifCompleted(pathAnimatedSnapshot) {
canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"),
};
imageData = [{ localPath: pathAnimatedSnapshot, href: href }];
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "addImages",
options: snapshotOptions,
image_data: imageData
- }));
+ });
});
}
function maybeDeleteSnapshotStories() {
@@ -655,28 +617,16 @@ function maybeDeleteSnapshotStories() {
});
storyIDsToMaybeDelete = [];
}
-function onTabletScreenChanged(type, url) {
- var wasInSnapshotReview = isInSnapshotReview;
- isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL);
- button.editProperties({ isActive: isInSnapshotReview });
- if (isInSnapshotReview !== wasInSnapshotReview) {
- if (isInSnapshotReview) {
- tablet.webEventReceived.connect(onMessage);
- } else {
- tablet.webEventReceived.disconnect(onMessage);
- }
- }
-}
function onUsernameChanged() {
fillImageDataFromPrevious();
isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "showPreviousImages",
options: snapshotOptions,
image_data: imageData,
canShare: canShare
- }));
+ });
});
if (isLoggedIn) {
if (shareAfterLogin) {
@@ -705,10 +655,10 @@ function onUsernameChanged() {
function snapshotLocationSet(location) {
if (location !== "") {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "snapshotLocationChosen"
- }));
+ });
}
}
@@ -733,36 +683,36 @@ function processRezPermissionChange(canRez) {
action = 'setPrintButtonDisabled';
}
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action : action
- }));
+ });
}
-button.clicked.connect(onButtonClicked);
-buttonConnected = true;
+function startup() {
+ ui = new AppUi({
+ buttonName: "SNAP",
+ sortOrder: 5,
+ home: Script.resolvePath("html/SnapshotReview.html"),
+ onOpened: fillImageDataFromPrevious,
+ onMessage: onMessage
+ });
-Window.snapshotShared.connect(snapshotUploaded);
-tablet.screenChanged.connect(onTabletScreenChanged);
-GlobalServices.myUsernameChanged.connect(onUsernameChanged);
-Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
+ Entities.canRezChanged.connect(updatePrintPermissions);
+ Entities.canRezTmpChanged.connect(updatePrintPermissions);
+ GlobalServices.myUsernameChanged.connect(onUsernameChanged);
+ Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
+ Window.snapshotShared.connect(snapshotUploaded);
+}
+startup();
-Entities.canRezChanged.connect(updatePrintPermissions);
-Entities.canRezTmpChanged.connect(updatePrintPermissions);
-
-Script.scriptEnding.connect(function () {
- if (buttonConnected) {
- button.clicked.disconnect(onButtonClicked);
- buttonConnected = false;
- }
- if (tablet) {
- tablet.removeButton(button);
- tablet.screenChanged.disconnect(onTabletScreenChanged);
- }
+function shutdown() {
Window.snapshotShared.disconnect(snapshotUploaded);
Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
+ GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
Entities.canRezChanged.disconnect(updatePrintPermissions);
Entities.canRezTmpChanged.disconnect(updatePrintPermissions);
-});
+}
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js
index 8fafac7685..804f838d04 100644
--- a/scripts/system/tablet-goto.js
+++ b/scripts/system/tablet-goto.js
@@ -1,9 +1,10 @@
"use strict";
-/*jslint vars:true, plusplus:true, forin:true*/
-/*global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print*/
+/* jslint vars:true, plusplus:true, forin:true */
+/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
+/* global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print */
//
-// goto.js
+// tablet-goto.js
// scripts/system/
//
// Created by Dante Ruiz on 8 February 2017
@@ -14,148 +15,118 @@
//
(function () { // BEGIN LOCAL_SCOPE
+var request = Script.require('request').request;
+var AppUi = Script.require('appUi');
+var DEBUG = false;
+function debug() {
+ if (!DEBUG) {
+ return;
+ }
+ print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
+}
- var request = Script.require('request').request;
- var DEBUG = false;
- function debug() {
- if (!DEBUG) {
+var stories = {}, pingPong = false;
+function expire(id) {
+ var options = {
+ uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id,
+ method: 'PUT',
+ json: true,
+ body: {expire: "true"}
+ };
+ request(options, function (error, response) {
+ debug('expired story', options, 'error:', error, 'response:', response);
+ if (error || (response.status !== 'success')) {
+ print("ERROR expiring story: ", error || response.status);
+ }
+ });
+}
+var PER_PAGE_DEBUG = 10;
+var PER_PAGE_NORMAL = 100;
+function pollForAnnouncements() {
+ // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments?
+ var actions = 'announcement';
+ var count = DEBUG ? PER_PAGE_DEBUG : PER_PAGE_NORMAL;
+ var options = [
+ 'now=' + new Date().toISOString(),
+ 'include_actions=' + actions,
+ 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
+ 'require_online=true',
+ 'protocol=' + encodeURIComponent(Window.protocolSignature()),
+ 'per_page=' + count
+ ];
+ var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&');
+ request({
+ uri: url
+ }, function (error, data) {
+ debug(url, error, data);
+ if (error || (data.status !== 'success')) {
+ print("Error: unable to get", url, error || data.status);
return;
}
- print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
- }
-
- var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml";
- var buttonName = "GOTO";
- var onGotoScreen = false;
- var shouldActivateButton = false;
- function ignore() { }
-
- var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- var NORMAL_ICON = "icons/tablet-icons/goto-i.svg";
- var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg";
- var WAITING_ICON = "icons/tablet-icons/goto-msg.svg";
- var button = tablet.addButton({
- icon: NORMAL_ICON,
- activeIcon: NORMAL_ACTIVE,
- text: buttonName,
- sortOrder: 8
- });
-
- function messagesWaiting(isWaiting) {
- button.editProperties({
- icon: isWaiting ? WAITING_ICON : NORMAL_ICON
- // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway.
- });
- }
-
- function onClicked() {
- if (onGotoScreen) {
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- shouldActivateButton = true;
- tablet.loadQMLSource(gotoQmlSource);
- onGotoScreen = true;
- }
- }
-
- function onScreenChanged(type, url) {
- ignore(type);
- if (url === gotoQmlSource) {
- onGotoScreen = true;
- shouldActivateButton = true;
- button.editProperties({isActive: shouldActivateButton});
- messagesWaiting(false);
- } else {
- shouldActivateButton = false;
- onGotoScreen = false;
- button.editProperties({isActive: shouldActivateButton});
- }
- }
- button.clicked.connect(onClicked);
- tablet.screenChanged.connect(onScreenChanged);
-
- var stories = {}, pingPong = false;
- function expire(id) {
- var options = {
- uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id,
- method: 'PUT',
- json: true,
- body: {expire: "true"}
- };
- request(options, function (error, response) {
- debug('expired story', options, 'error:', error, 'response:', response);
- if (error || (response.status !== 'success')) {
- print("ERROR expiring story: ", error || response.status);
+ var didNotify = false, key;
+ pingPong = !pingPong;
+ data.user_stories.forEach(function (story) {
+ var stored = stories[story.id], storedOrNew = stored || story;
+ debug('story exists:', !!stored, storedOrNew);
+ if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) {
+ if (storedOrNew.audience === 'for_connections') { // Only expire if we haven't already done so.
+ expire(story.id);
+ }
+ return; // before marking
}
- });
- }
- function pollForAnnouncements() {
- // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments?
- var actions = 'announcement';
- var count = DEBUG ? 10 : 100;
- var options = [
- 'now=' + new Date().toISOString(),
- 'include_actions=' + actions,
- 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
- 'require_online=true',
- 'protocol=' + encodeURIComponent(Window.protocolSignature()),
- 'per_page=' + count
- ];
- var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&');
- request({
- uri: url
- }, function (error, data) {
- debug(url, error, data);
- if (error || (data.status !== 'success')) {
- print("Error: unable to get", url, error || data.status);
+ storedOrNew.pingPong = pingPong;
+ if (stored) { // already seen
return;
}
- var didNotify = false, key;
- pingPong = !pingPong;
- data.user_stories.forEach(function (story) {
- var stored = stories[story.id], storedOrNew = stored || story;
- debug('story exists:', !!stored, storedOrNew);
- if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) {
- if (storedOrNew.audience == 'for_connections') { // Only expire if we haven't already done so.
- expire(story.id);
- }
- return; // before marking
- }
- storedOrNew.pingPong = pingPong;
- if (stored) { // already seen
- return;
- }
- stories[story.id] = story;
- var message = story.username + " " + story.action_string + " in " + story.place_name + ". Open GOTO to join them.";
- Window.displayAnnouncement(message);
- didNotify = true;
- });
- for (key in stories) { // Any story we were tracking that was not marked, has expired.
- if (stories[key].pingPong !== pingPong) {
- debug('removing story', key);
- delete stories[key];
- }
- }
- if (didNotify) {
- messagesWaiting(true);
- if (HMD.isHandControllerAvailable()) {
- var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands
- Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND);
- }
- } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired.
- messagesWaiting(false);
- }
+ stories[story.id] = story;
+ var message = story.username + " " + story.action_string + " in " +
+ story.place_name + ". Open GOTO to join them.";
+ Window.displayAnnouncement(message);
+ didNotify = true;
});
- }
- var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? 10 : 60) * 1000;
- var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS);
-
- Script.scriptEnding.connect(function () {
- Script.clearInterval(pollTimer);
- button.clicked.disconnect(onClicked);
- tablet.removeButton(button);
- tablet.screenChanged.disconnect(onScreenChanged);
+ for (key in stories) { // Any story we were tracking that was not marked, has expired.
+ if (stories[key].pingPong !== pingPong) {
+ debug('removing story', key);
+ delete stories[key];
+ }
+ }
+ if (didNotify) {
+ ui.messagesWaiting(true);
+ if (HMD.isHandControllerAvailable()) {
+ var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands
+ Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND);
+ }
+ } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired.
+ ui.messagesWaiting(false);
+ }
});
+}
+var MS_PER_SEC = 1000;
+var DEBUG_POLL_TIME_SEC = 10;
+var NORMAL_POLL_TIME_SEC = 60;
+var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? DEBUG_POLL_TIME_SEC : NORMAL_POLL_TIME_SEC) * MS_PER_SEC;
+var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS);
+function gotoOpened() {
+ ui.messagesWaiting(false);
+}
+
+var ui;
+var GOTO_QML_SOURCE = "hifi/tablet/TabletAddressDialog.qml";
+var BUTTON_NAME = "GOTO";
+function startup() {
+ ui = new AppUi({
+ buttonName: BUTTON_NAME,
+ sortOrder: 8,
+ onOpened: gotoOpened,
+ home: GOTO_QML_SOURCE
+ });
+}
+
+function shutdown() {
+ Script.clearInterval(pollTimer);
+}
+
+startup();
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE