From ec0b0ab464fcb12b0e01b86cb837470259be2069 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Sep 2018 12:59:53 -0700 Subject: [PATCH 01/29] fix acceleration spread --- .../src/RenderableParticleEffectEntityItem.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) 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; } From 2d2a4804f7790f41a8b9af373c5274ff73e7c84c Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 6 Sep 2018 17:49:52 -0300 Subject: [PATCH 02/29] Add android settings screen. Add AEC setting --- android/app/src/main/AndroidManifest.xml | 10 +++ android/app/src/main/cpp/native.cpp | 46 ++++++++++++++ .../hifiinterface/InterfaceActivity.java | 7 +++ .../hifiinterface/MainActivity.java | 23 +++++++ .../fragment/SettingsFragment.java | 63 +++++++++++++++++++ .../receiver/HeadsetStateReceiver.java | 20 ++++++ .../app/src/main/res/menu/menu_navigation.xml | 5 ++ android/app/src/main/res/values/strings.xml | 5 ++ android/app/src/main/res/xml/settings.xml | 11 ++++ interface/src/AndroidHelper.cpp | 17 +++++ interface/src/AndroidHelper.h | 1 + libraries/audio-client/src/AudioClient.cpp | 6 +- libraries/audio-client/src/AudioClient.h | 8 +++ 13 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java create mode 100644 android/app/src/main/res/xml/settings.xml 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 ce5af01f29..6b44b2dc7a 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -355,5 +355,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 f161783d6a..08e66a2f42 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); @@ -151,6 +154,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 @@ -161,6 +166,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW } else { nativeEnterBackground(); } + unregisterReceiver(headsetStateReceiver); //gvrApi.pauseTracking(); } @@ -183,6 +189,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 db6f0fca24..4c6d05a3e8 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -33,6 +33,7 @@ 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.task.DownloadProfileImageTask; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, @@ -80,6 +81,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); @@ -108,6 +111,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": @@ -151,6 +164,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(); @@ -241,6 +261,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..29bc1c49f2 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -0,0 +1,20 @@ +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); + + Log.d("[HEADSET] " , "BR - Wired headset on:" + audioManager.isWiredHeadsetOn()); + 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 b158aba59d..abde15f484 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -29,4 +29,9 @@ tagFragmentLogin tagFragmentPolicy tagFragmentPeople + tagSettings + 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/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 419382f2cb..35bf094591 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,6 +10,7 @@ // #include "AndroidHelper.h" #include +#include #include "Application.h" #if defined(qApp) @@ -18,6 +19,7 @@ #define qApp (static_cast(QCoreApplication::instance())) AndroidHelper::AndroidHelper() { + qRegisterMetaType("QAudio::Mode"); } AndroidHelper::~AndroidHelper() { @@ -56,3 +58,18 @@ void AndroidHelper::processURL(const QString &url) { qApp->acceptURL(url); } } + +void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { +#if defined (Q_OS_ANDROID) + auto audioClient = DependencyManager::get(); + if (audioClient) { + QAudioDeviceInfo activeDev = audioClient->getActiveAudioDevice(QAudio::AudioInput); + Setting::Handle enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) { + QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION)); + } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) { + QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION)); + } + } +#endif +} diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index 03d92f91d9..11b84e4025 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -29,6 +29,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/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6a9363f309..e23b8ac3cd 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 @@ -451,9 +450,12 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { + Setting::Handle enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { - if (inputDevice.deviceName() == VOICE_RECOGNITION) { + if ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) || + (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) { return inputDevice; } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8599c990a3..fa7ac40a16 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -64,6 +64,14 @@ #pragma warning( pop ) #endif +#if defined (Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" +#define VOICE_COMMUNICATION "voicecommunication" + +#define ANDROID_SETTINGS_GROUP "Android" +#define SETTING_AEC_KEY "aec" +#endif + class QAudioInput; class QAudioOutput; class QIODevice; From 0e079e78e61c7c0814c8439de9499e1babe712a6 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 10 Sep 2018 16:16:04 -0700 Subject: [PATCH 03/29] correct transparent parabola shader --- interface/src/raypick/ParabolaPointer.cpp | 4 ++-- .../render-utils/src/parabola_translucent.slf | 18 ++++++++++++++++++ .../src/render-utils/parabola_translucent.slp | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 libraries/render-utils/src/parabola_translucent.slf create mode 100644 libraries/render-utils/src/render-utils/parabola_translucent.slp 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/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 From 1aaa90675c1f4aaa137e088228f3e6221d135d9f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 11 Sep 2018 12:04:39 -0700 Subject: [PATCH 04/29] Snapshot.js now uses AppUi --- scripts/modules/appUi.js | 17 ++++- scripts/system/snapshot.js | 141 ++++++++++++------------------------- 2 files changed, 61 insertions(+), 97 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 6d2986768a..76a06424db 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -54,6 +54,17 @@ function AppUi(properties) { that.tablet.gotoWebScreen(url, that.inject); } }; + that.openOnTop = function openOnTop(url, optionalInject) { // Opens some app on top of the current app + if (that.isQML(url)) { + that.tablet.loadQMLOnTop(url); + } else { + if (optionalInject) { + that.tablet.loadWebScreenOnTop(url, optionalInject); + } else { + that.tablet.loadWebScreenOnTop(url); + } + } + } that.close = function close() { // How to close the app. that.currentUrl = ""; // for toolbar-mode: go back to home screen, this will close the window. @@ -69,7 +80,11 @@ function AppUi(properties) { activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton }); }; - that.isQML = function isQML() { // We set type property in onClick. + that.isQML = function isQML(optionalUrl) { // We set type property in onClick. + if (optionalUrl) { + var type = /.qml$/.test(optionalUrl) ? 'QML' : 'Web'; + return type === 'QML'; + } return that.type === 'QML'; }; that.eventSignal = function eventSignal() { // What signal to hook onMessage to. diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37270f896e..907dcd75c1 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -12,23 +12,13 @@ (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 +42,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 +61,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 +111,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.openOnTop("hifi/tablet/TabletGeneralPreferences.qml"); } break; case 'captureStillAndGif': @@ -284,7 +271,6 @@ var POLAROID_RATE_LIMIT_MS = 1000; var polaroidPrintingIsRateLimited = false; function printToPolaroid(image_url) { - // Rate-limit printing if (polaroidPrintingIsRateLimited) { return; @@ -376,19 +362,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 +382,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 +402,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 +444,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 +507,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 +524,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 +537,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 +568,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 +592,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 +616,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 +654,10 @@ function onUsernameChanged() { function snapshotLocationSet(location) { if (location !== "") { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendMessage({ type: "snapshot", action: "snapshotLocationChosen" - })); + }); } } @@ -733,36 +682,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 From 3dabd392ef5e0bba77bee657f73b9f9acb44f9dc Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 11 Sep 2018 12:21:02 -0700 Subject: [PATCH 05/29] HELP app now uses AppUi --- scripts/system/help.js | 55 +++++++++--------------------------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/scripts/system/help.js b/scripts/system/help.js index aaeb82721c..325a2c243b 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -12,50 +12,17 @@ // /* 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"; +(function () { // BEGIN LOCAL_SCOPE + var AppUi = Script.require('appUi'); + 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 - }); - - 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; - } + var HELP_BUTTON_NAME = "HELP"; + function startup() { + ui = new AppUi({ + buttonName: HELP_BUTTON_NAME, + sortOrder: 6, + home: HELP_URL + }); } - - 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 From ea04ee6d359d8bfbb68ab80bec93549635b78fb8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 11 Sep 2018 12:37:53 -0700 Subject: [PATCH 06/29] Wallet now uses AppUi --- .../qml/hifi/commerce/wallet/Wallet.qml | 4 +- scripts/modules/appUi.js | 13 +- scripts/system/commerce/wallet.js | 169 ++++-------------- scripts/system/snapshot.js | 2 +- 4 files changed, 49 insertions(+), 139 deletions(-) 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/scripts/modules/appUi.js b/scripts/modules/appUi.js index 76a06424db..fd13075eb6 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -54,7 +54,18 @@ function AppUi(properties) { that.tablet.gotoWebScreen(url, that.inject); } }; - that.openOnTop = function openOnTop(url, optionalInject) { // Opens some app on top of the current app + that.openNewApp = function openNewApp(url, optionalInject) { // Opens some app and replaces the current app + if (that.isQML(url)) { + that.tablet.pushOntoStack(url); + } else { + if (optionalInject) { + that.tablet.gotoWebScreen(url, optionalInject); + } else { + that.tablet.gotoWebScreen(url); + } + } + } + that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) { // Opens some app on top of the current app (on desktop, opens new window) if (that.isQML(url)) { that.tablet.loadQMLOnTop(url); } else { diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 5939b36438..d3109e3d66 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -16,6 +16,7 @@ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); Script.include("/~/system/libraries/connectionUtils.js"); + var AppUi = Script.require('appUi'); var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; @@ -190,7 +191,7 @@ method: 'updateSelectedRecipientUsername', userName: username === "" ? "unknown username" : username }; - sendToQml(message); + ui.sendMessage(message); } } function handleClick(pickRay) { @@ -208,7 +209,7 @@ displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', userName: '' }; - sendToQml(message); + ui.sendMessage(message); ExtendedOverlay.some(function (overlay) { var id = overlay.key; @@ -299,39 +300,6 @@ 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; - } - 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; @@ -419,28 +387,28 @@ // -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 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': - tablet.gotoHomeScreen(); + ui.close(); break; case 'walletSetup_cancelClicked': switch (message.referrer) { case '': // User clicked "Wallet" app case undefined: case null: - tablet.gotoHomeScreen(); + ui.close(); break; case 'purchases': case 'marketplace cta': case 'mainPage': - tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(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); + ui.openNewApp(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); break; } break; @@ -451,27 +419,18 @@ 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); + ui.openNewApp(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'goToPurchases_fromWalletHome': case 'goToPurchases': - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); break; case 'goToMarketplaceMainPage': - tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'goToMarketplaceItemPage': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'refreshConnections': print('Refreshing Connections...'); @@ -526,93 +485,43 @@ } } - // 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 walletOpened() { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); } - // 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(); - } + function walletClosed() { + off(); } // // Manage the connection between the button and the window. // - var button; - var buttonName = "WALLET"; - var tablet = null; + var BUTTON_NAME = "WALLET"; + var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; var walletEnabled = Settings.getValue("commerce", true); function startup() { + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: WALLET_QML_SOURCE, + onOpened: walletOpened, + onClosed: walletClosed, + onMessage: fromQml + }); 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(); + 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; @@ -621,15 +530,7 @@ } function shutdown() { GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - button.clicked.disconnect(onButtonClicked); - tablet.removeButton(button); deleteSendMoneyParticleEffect(); - if (tablet) { - tablet.screenChanged.disconnect(onTabletScreenChanged); - if (onWalletScreen) { - tablet.gotoHomeScreen(); - } - } off(); } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 907dcd75c1..fe952ab1a7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -111,7 +111,7 @@ function onMessage(message) { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); } else { - ui.openOnTop("hifi/tablet/TabletGeneralPreferences.qml"); + ui.openNewAppOnTop("hifi/tablet/TabletGeneralPreferences.qml"); } break; case 'captureStillAndGif': From 6d345e506ba64d2ca2ff0e12acac0352c1d73600 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 11 Sep 2018 19:42:12 -0700 Subject: [PATCH 07/29] Marketplaces.js WIP --- scripts/system/html/js/marketplacesInject.js | 1 + scripts/system/marketplaces/marketplaces.js | 231 +++++-------------- 2 files changed, 61 insertions(+), 171 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 9b91d06d41..a68556d771 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -708,6 +708,7 @@ function onLoad() { EventBridge.scriptEventReceived.connect(function (message) { + message = JSON.stringify(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) { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b4f05193f..66ddd0f09f 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -16,14 +16,11 @@ var selectionDisplay = null; // for gridTool.js to ignore (function () { // BEGIN LOCAL_SCOPE - - Script.include("/~/system/libraries/WebTablet.js"); + var AppUi = Script.require('appUi'); Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); var METAVERSE_SERVER_URL = Account.metaverseServerURL; - var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; - 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"; @@ -32,9 +29,6 @@ var selectionDisplay = null; // for gridTool.js to ignore var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); - 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"; - // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; var CLARA_IO_STATUS = "CLARA.IO STATUS"; @@ -58,45 +52,13 @@ var selectionDisplay = null; // for gridTool.js to ignore 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) - }); + ui.sendToHtml(CLARA_IO_CANCEL_DOWNLOAD); } } function onCanWriteAssetsChanged() { var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); - tablet.emitScriptEvent(message); + ui.sendToHtml(message); } @@ -119,13 +81,13 @@ var selectionDisplay = null; // for gridTool.js to ignore } function openWallet() { - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + ui.openNewApp(MARKETPLACE_WALLET_QML_PATH); } function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { - wireEventBridge(true); + ui.wireEventBridge(true); var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); - sendToQml({ + ui.sendMessage({ method: 'inspectionCertificate_setCertificateId', entityId: currentEntityWithContextOverlay, certificateId: certificateId @@ -134,13 +96,13 @@ var selectionDisplay = null; // for gridTool.js to ignore function onUsernameChanged() { if (onMarketplaceScreen) { - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); } } var userHasUpdates = false; function sendCommerceSettings() { - tablet.emitScriptEvent(JSON.stringify({ + ui.sendToHtml({ type: "marketplaces", action: "commerceSetting", data: { @@ -150,7 +112,7 @@ var selectionDisplay = null; // for gridTool.js to ignore metaverseServerURL: Account.metaverseServerURL, messagesWaiting: userHasUpdates } - })); + }); } // BEGIN AVATAR SELECTOR LOGIC @@ -323,7 +285,7 @@ var selectionDisplay = null; // for gridTool.js to ignore method: 'updateSelectedRecipientUsername', userName: username === "" ? "unknown username" : username }; - sendToQml(message); + ui.sendMessage(message); } } function handleClick(pickRay) { @@ -341,7 +303,7 @@ var selectionDisplay = null; // for gridTool.js to ignore displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', userName: '' }; - sendToQml(message); + ui.sendMessage(message); ExtendedOverlay.some(function (overlay) { var id = overlay.key; @@ -627,9 +589,9 @@ var selectionDisplay = null; // for gridTool.js to ignore var filterText; // Used for updating Purchases QML function onMessage(message) { if (message === GOTO_DIRECTORY) { - tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); } else if (message === QUERY_CAN_WRITE_ASSETS) { - tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + ui.sendToHtml(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) { @@ -655,9 +617,9 @@ var selectionDisplay = null; // for gridTool.js to ignore } else { var parsedJsonMessage = JSON.parse(message); if (parsedJsonMessage.type === "CHECKOUT") { - wireEventBridge(true); - tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); - sendToQml({ + ui.wireEventBridge(true); + ui.openNewApp(MARKETPLACE_CHECKOUT_QML_PATH); + ui.sendMessage({ method: 'updateCheckoutQML', params: parsedJsonMessage }); @@ -666,12 +628,12 @@ var selectionDisplay = null; // for gridTool.js to ignore } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); } else if (parsedJsonMessage.type === "WALLET_SETUP") { - wireEventBridge(true); - sendToQml({ + ui.wireEventBridge(true); + ui.sendMessage({ method: 'updateWalletReferrer', referrer: "marketplace cta" }); @@ -679,40 +641,14 @@ var selectionDisplay = null; // for gridTool.js to ignore } else if (parsedJsonMessage.type === "MY_ITEMS") { referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - wireEventBridge(true); - sendToQml({ + ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); + ui.wireEventBridge(true); + ui.sendMessage({ 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; @@ -813,44 +749,40 @@ var selectionDisplay = null; // for gridTool.js to ignore openWallet(); break; case 'purchases_walletNotSetUp': - wireEventBridge(true); - sendToQml({ + ui.wireEventBridge(true); + ui.sendMessage({ method: 'updateWalletReferrer', referrer: "purchases" }); openWallet(); break; case 'checkout_walletNotSetUp': - wireEventBridge(true); - sendToQml({ + ui.wireEventBridge(true); + ui.sendMessage({ 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(); + ui.openNewApp(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'header_goToPurchases': case 'checkout_goToPurchases': referrerURL = MARKETPLACE_URL_INITIAL; filterText = message.filterText; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); break; case 'checkout_itemLinkClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'checkout_continueShopping': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - //tablet.popFromStack(); + ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'purchases_itemInfoClicked': var itemId = message.itemId; if (itemId && itemId !== "") { - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); } break; case 'checkout_rezClicked': @@ -859,13 +791,13 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': - tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'purchases_goToMarketplaceClicked': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'updateItemClicked': - tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition, + ui.openNewApp(message.upgradeUrl + "?edition=" + message.itemEdition, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'giftAsset': @@ -873,7 +805,7 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'needsLogIn_loginClicked': openLoginWindow(); @@ -894,26 +826,26 @@ var selectionDisplay = null; // for gridTool.js to ignore maybeEnableHMDPreview(); break; case 'purchases_openGoTo': - tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); + ui.openNewApp("hifi/tablet/TabletAddressDialog.qml"); break; case 'purchases_itemCertificateClicked': setCertificateInfo("", message.itemCertificateId); break; case 'inspectionCertificate_closeClicked': - tablet.gotoHomeScreen(); + ui.close(); break; case 'inspectionCertificate_requestOwnershipVerification': ContextOverlay.requestOwnershipVerification(message.entity); break; case 'inspectionCertificate_showInMarketplaceClicked': - tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); + ui.openNewApp(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'header_myItemsClicked': referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; - tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); - wireEventBridge(true); - sendToQml({ + ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); + ui.wireEventBridge(true); + ui.sendMessage({ method: 'purchases_showMyItems' }); break; @@ -949,7 +881,7 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'wallet_availableUpdatesReceived': case 'purchases_availableUpdatesReceived': userHasUpdates = message.numUpdates > 0; - messagesWaiting(userHasUpdates); + ui.messagesWaiting(userHasUpdates); break; case 'purchases_updateWearables': var currentlyWornWearables = []; @@ -968,7 +900,7 @@ var selectionDisplay = null; // for gridTool.js to ignore } } - sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables }); + ui.sendMessage({ method: 'updateWearables', wornWearables: currentlyWornWearables }); break; case 'sendAsset_sendPublicly': if (message.assetName !== "") { @@ -998,38 +930,12 @@ var selectionDisplay = null; // for gridTool.js to ignore } } - // 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. + var onMarketplaceScreen = false; var onWalletScreen = false; var onCommerceScreen = false; function onTabletScreenChanged(type, url) { @@ -1044,20 +950,18 @@ var selectionDisplay = null; // for gridTool.js to ignore onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; - wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + ui.wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { - sendToQml({ + ui.sendMessage({ 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 }); - } + ui.buttonActive((onMarketplaceScreen || onCommerceScreen) && !onWalletScreen); + if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { ContextOverlay.isInMarketplaceInspectionMode = true; } else { @@ -1075,38 +979,32 @@ var selectionDisplay = null; // for gridTool.js to ignore isWired = true; Wallet.refreshWalletStatus(); } else { - off(); - sendToQml({ + ui.sendMessage({ method: 'inspectionCertificate_resetCert' }); + off(); } } // // 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"; + 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. function startup() { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - marketplaceButton = tablet.addButton({ - icon: NORMAL_ICON, - activeIcon: NORMAL_ACTIVE, - text: buttonName, - sortOrder: 9 + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 9, + inject: MARKETPLACES_INJECT_SCRIPT_URL, + home: MARKETPLACE_URL_INITIAL, + onMessage: fromQml }); 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); @@ -1124,6 +1022,7 @@ var selectionDisplay = null; // for gridTool.js to ignore isWired = false; } + if (isUpdateOverlaysWired) { Script.update.disconnect(updateOverlays); isUpdateOverlaysWired = false; @@ -1138,18 +1037,8 @@ var selectionDisplay = null; // for gridTool.js to ignore 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); - - if (tablet) { - tablet.screenChanged.disconnect(onTabletScreenChanged); - if (onMarketplaceScreen) { - tablet.gotoHomeScreen(); - } - } - off(); } From 2943502c9b5d0d9630c97b76793ffa5b3c205561 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 12 Sep 2018 16:36:04 -0300 Subject: [PATCH 08/29] Headset plug/unplug detection --- .../receiver/HeadsetStateReceiver.java | 2 -- interface/src/AndroidHelper.cpp | 8 +---- libraries/audio-client/src/AudioClient.cpp | 32 +++++++++++++++++-- libraries/audio-client/src/AudioClient.h | 11 +++++-- 4 files changed, 39 insertions(+), 14 deletions(-) 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 index 29bc1c49f2..5645912d73 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -13,8 +13,6 @@ public class HeadsetStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - - Log.d("[HEADSET] " , "BR - Wired headset on:" + audioManager.isWiredHeadsetOn()); notifyHeadsetOn(audioManager.isWiredHeadsetOn()); } } diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 35bf094591..400085a62a 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -63,13 +63,7 @@ void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { #if defined (Q_OS_ANDROID) auto audioClient = DependencyManager::get(); if (audioClient) { - QAudioDeviceInfo activeDev = audioClient->getActiveAudioDevice(QAudio::AudioInput); - Setting::Handle enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); - if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) { - QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION)); - } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) { - QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION)); - } + QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn)); } #endif } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e23b8ac3cd..f0ba0307cc 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -209,6 +209,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 @@ -450,12 +451,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { - Setting::Handle enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false); + 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 ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) || - (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) { + if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || + ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { return inputDevice; } } @@ -1632,6 +1635,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 fa7ac40a16..b1ccb496b6 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -68,8 +68,7 @@ #define VOICE_RECOGNITION "voicerecognition" #define VOICE_COMMUNICATION "voicecommunication" -#define ANDROID_SETTINGS_GROUP "Android" -#define SETTING_AEC_KEY "aec" +#define SETTING_AEC_KEY "Android/aec" #endif class QAudioInput; @@ -176,6 +175,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(); @@ -224,6 +227,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); @@ -285,6 +291,7 @@ private: #ifdef Q_OS_ANDROID QTimer _checkInputTimer; long _inputReadsSinceLastCheck = 0l; + bool _isHeadsetPluggedIn; #endif class Gate { From 01073ca18d378a8e04f67cabb1d786f373a74aa6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 12 Sep 2018 15:32:51 -0700 Subject: [PATCH 09/29] Lots of changes; marketplaces.js working now --- .../ui/overlays/ContextOverlayInterface.cpp | 34 - .../src/ui/overlays/ContextOverlayInterface.h | 2 - scripts/modules/appUi.js | 175 +- scripts/system/commerce/wallet.js | 961 ++++---- scripts/system/html/js/marketplacesInject.js | 290 +-- scripts/system/marketplaces/marketplaces.js | 1994 +++++++++-------- scripts/system/pal.js | 2 +- 7 files changed, 1747 insertions(+), 1711 deletions(-) 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/scripts/modules/appUi.js b/scripts/modules/appUi.js index fd13075eb6..3a70a69d4d 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 // @@ -31,53 +31,49 @@ 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 = that.inject; + if (optionalUrl && optionalInject) { + inject = optionalInject; + } + + if (that.isQMLUrl(url)) { that.tablet.loadQMLSource(url); } else { - that.tablet.gotoWebScreen(url, that.inject); + that.tablet.gotoWebScreen(url, inject); } }; - that.openNewApp = function openNewApp(url, optionalInject) { // Opens some app and replaces the current app - if (that.isQML(url)) { - that.tablet.pushOntoStack(url); - } else { - if (optionalInject) { - that.tablet.gotoWebScreen(url, optionalInject); - } else { - that.tablet.gotoWebScreen(url); - } - } - } - that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) { // Opens some app on top of the current app (on desktop, opens new window) - if (that.isQML(url)) { + // 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 { - if (optionalInject) { - that.tablet.loadWebScreenOnTop(url, optionalInject); - } else { - that.tablet.loadWebScreenOnTop(url); - } + 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(); }; @@ -91,15 +87,40 @@ function AppUi(properties) { activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton }); }; - that.isQML = function isQML(optionalUrl) { // We set type property in onClick. - if (optionalUrl) { - var type = /.qml$/.test(optionalUrl) ? 'QML' : 'Web'; - return type === 'QML'; - } - return that.type === 'QML'; + that.isQMLUrl = function isQMLUrl(url) { + var type = /.qml$/.test(url) ? 'QML' : 'Web'; + return type === 'QML'; }; - that.eventSignal = function eventSignal() { // What signal to hook onMessage to. - return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived; + that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() { + return that.currentVisibleScreenType === 'QML'; + }; + + // 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: @@ -126,65 +147,55 @@ 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.hasQmlEventBridge = false; + that.hasHtmlEventBridge = 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; } else { that.sendMessage = that.ignore; } - 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.hasQmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.fromQml); + that.tablet.fromQml.connect(that.onMessage); + that.hasQmlEventBridge = true; + } else if (!isCurrentlyOnQMLScreen && !that.hasHtmlEventBridge) { + console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.connect(that.fromHtml); + that.hasHtmlEventBridge = true; } } else { - if (that.hasEventBridge) { - console.debug(that.buttonName, 'disconnecting', that.eventSignal()); - that.eventSignal().disconnect(handler); - that.hasEventBridge = false; + if (that.hasQmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml); + that.tablet.fromQml.disconnect(that.onMessage); + that.hasQmlEventBridge = false; + } + if (that.hasHtmlEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived); + that.tablet.webEventReceived.disconnect(that.fromHtml); + that.hasHtmlEventBridge = false; } } }; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index d3109e3d66..213e008bdc 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -14,530 +14,529 @@ /* global getConnectionData */ (function () { // BEGIN LOCAL_SCOPE - Script.include("/~/system/libraries/accountUtils.js"); - Script.include("/~/system/libraries/connectionUtils.js"); - var AppUi = Script.require('appUi'); +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 - }; - ui.sendMessage(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: '' - }; - ui.sendMessage(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 handleMouseMove(pickRay) { // given the pickRay, just do the hover logic - ExtendedOverlay.applyPickRay(pickRay, function (overlay) { - overlay.hover(true); - }, function () { - ExtendedOverlay.unHover(); + }); +}; + +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 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); - // 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; + ExtendedOverlay.some(function (overlay) { + var id = overlay.key; + var selected = ExtendedOverlay.isSelected(id); + overlay.select(selected); + }); - 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; - } + 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 { - 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; + // nothing should hover, so + ExtendedOverlay.unHover(); 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); } + 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) }; +// 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 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' }; +} +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 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 - }); - } - } +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 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 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.openNewApp(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - default: // User needs to return to an individual marketplace item URL - ui.openNewApp(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 'transactionHistory_linkClicked': - ui.openNewApp(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'goToPurchases_fromWalletHome': - case 'goToPurchases': - ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); - break; - case 'goToMarketplaceMainPage': - ui.openNewApp(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'goToMarketplaceItemPage': - ui.openNewApp(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 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 BUTTON_NAME = "WALLET"; - var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; - var walletEnabled = Settings.getValue("commerce", true); - function startup() { - ui = new AppUi({ - buttonName: BUTTON_NAME, - sortOrder: 10, - home: WALLET_QML_SOURCE, - onOpened: walletOpened, - onClosed: walletClosed, - onMessage: fromQml +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 }); - GlobalServices.myUsernameChanged.connect(onUsernameChanged); } - var isUpdateOverlaysWired = false; - function off() { - Users.usernameFromIDReply.disconnect(usernameFromIDReply); - Controller.mousePressEvent.disconnect(handleMouseEvent); - Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - triggerMapping.disable(); - triggerPressMapping.disable(); +} +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 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; + } + 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(); + 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 shutdown() { - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); - deleteSendMoneyParticleEffect(); - off(); +} + +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 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); +} +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(); + off(); +} - // - // Run the functions. - // - startup(); - Script.scriptEnding.connect(shutdown); - +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index a68556d771..7821edee33 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,26 +728,22 @@ function onLoad() { EventBridge.scriptEventReceived.connect(function (message) { - message = JSON.stringify(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(); } } }); @@ -740,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/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 66ddd0f09f..13ad1f6b69 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -16,1036 +16,1082 @@ 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"); +var AppUi = Script.require('appUi'); +Script.include("/~/system/libraries/gridTool.js"); +Script.include("/~/system/libraries/connectionUtils.js"); - var METAVERSE_SERVER_URL = Account.metaverseServerURL; - 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")); +var METAVERSE_SERVER_URL = Account.metaverseServerURL; +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 = "hifi/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"; +// 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 CLARA_DOWNLOAD_TITLE = "Preparing Download"; - var messageBox = null; - var isDownloadBeingCancelled = false; +var CLARA_DOWNLOAD_TITLE = "Preparing Download"; +var messageBox = null; +var isDownloadBeingCancelled = false; - var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel - var NO_BUTTON = 0; // QMessageBox::NoButton +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."; +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; - ui.sendToHtml(CLARA_IO_CANCEL_DOWNLOAD); - } - } - - function onCanWriteAssetsChanged() { - var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); - ui.sendToHtml(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() { - ui.openNewApp(MARKETPLACE_WALLET_QML_PATH); - } - - function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { - ui.wireEventBridge(true); - var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); - ui.sendMessage({ - method: 'inspectionCertificate_setCertificateId', - entityId: currentEntityWithContextOverlay, - certificateId: certificateId - }); - } - - function onUsernameChanged() { - if (onMarketplaceScreen) { - ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - } - } - - var userHasUpdates = false; - function sendCommerceSettings() { +function onMessageBoxClosed(id, button) { + if (id === messageBox && button === CANCEL_BUTTON) { + isDownloadBeingCancelled = true; + messageBox = null; ui.sendToHtml({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", true), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1, - metaverseServerURL: Account.metaverseServerURL, - messagesWaiting: userHasUpdates - } + type: CLARA_IO_CANCEL_DOWNLOAD }); } +} - // 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 }; +function onCanWriteAssetsChanged() { + ui.sendToHtml({ + type: CAN_WRITE_ASSETS, + canWriteAssets: Entities.canWriteAssets() + }); +} - 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 - }; - ui.sendMessage(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: '' - }; - ui.sendMessage(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) { - ui.openNewApp(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); - } else if (message === QUERY_CAN_WRITE_ASSETS) { - ui.sendToHtml(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") { - ui.wireEventBridge(true); - ui.openNewApp(MARKETPLACE_CHECKOUT_QML_PATH); - ui.sendMessage({ - method: 'updateCheckoutQML', - params: parsedJsonMessage - }); - } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - sendCommerceSettings(); - } else if (parsedJsonMessage.type === "PURCHASES") { - referrerURL = parsedJsonMessage.referrerURL; - filterText = ""; - ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); - } else if (parsedJsonMessage.type === "LOGIN") { - openLoginWindow(); - } else if (parsedJsonMessage.type === "WALLET_SETUP") { - ui.wireEventBridge(true); - ui.sendMessage({ - method: 'updateWalletReferrer', - referrer: "marketplace cta" - }); - openWallet(); - } else if (parsedJsonMessage.type === "MY_ITEMS") { - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = ""; - ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); - ui.wireEventBridge(true); - ui.sendMessage({ - method: 'purchases_showMyItems' - }); - } - } - } - 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; } - function deleteSendAssetParticleEffect() { - if (sendAssetParticleEffectUpdateTimer) { - Script.clearInterval(sendAssetParticleEffectUpdateTimer); - sendAssetParticleEffectUpdateTimer = null; - } - if (sendAssetParticleEffect) { - sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect); - } - sendAssetRecipient = null; + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return 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); + 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; } - // 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': - ui.wireEventBridge(true); - ui.sendMessage({ - method: 'updateWalletReferrer', - referrer: "purchases" - }); - openWallet(); - break; - case 'checkout_walletNotSetUp': - ui.wireEventBridge(true); - ui.sendMessage({ - method: 'updateWalletReferrer', - referrer: message.referrer === "itemPage" ? message.itemId : message.referrer - }); - openWallet(); - break; - case 'checkout_cancelClicked': - ui.openNewApp(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'header_goToPurchases': - case 'checkout_goToPurchases': - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = message.filterText; - ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); - break; - case 'checkout_itemLinkClicked': - ui.openNewApp(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'checkout_continueShopping': - ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'purchases_itemInfoClicked': - var itemId = message.itemId; - if (itemId && itemId !== "") { - ui.openNewApp(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': - ui.openNewApp(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'purchases_goToMarketplaceClicked': - ui.openNewApp(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'updateItemClicked': - ui.openNewApp(message.upgradeUrl + "?edition=" + message.itemEdition, - MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'giftAsset': - - break; - case 'passphrasePopup_cancelClicked': - case 'needsLogIn_cancelClicked': - ui.openNewApp(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': - ui.openNewApp("hifi/tablet/TabletAddressDialog.qml"); - break; - case 'purchases_itemCertificateClicked': - setCertificateInfo("", message.itemCertificateId); - break; - case 'inspectionCertificate_closeClicked': - ui.close(); - break; - case 'inspectionCertificate_requestOwnershipVerification': - ContextOverlay.requestOwnershipVerification(message.entity); - break; - case 'inspectionCertificate_showInMarketplaceClicked': - ui.openNewApp(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'header_myItemsClicked': - referrerURL = MARKETPLACE_URL_INITIAL; - filterText = ""; - ui.openNewApp(MARKETPLACE_PURCHASES_QML_PATH); - ui.wireEventBridge(true); - ui.sendMessage({ - 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; + 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; + } + } } } - 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.sendMessage({ 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: 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 onMarketplaceScreen = false; - 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(); - } - - onCommerceScreen = onCommerceScreenNow; - onWalletScreen = onWalletScreenNow; - ui.wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); - - if (url === MARKETPLACE_PURCHASES_QML_PATH) { - ui.sendMessage({ - method: 'updatePurchases', - referrerURL: referrerURL, - filterText: filterText - }); - } - - ui.buttonActive((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 { - ui.sendMessage({ - method: 'inspectionCertificate_resetCert' + } + } + + 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 }); - off(); - } - } - // - // 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. - function startup() { - ui = new AppUi({ - buttonName: BUTTON_NAME, - sortOrder: 9, - inject: MARKETPLACES_INJECT_SCRIPT_URL, - home: MARKETPLACE_URL_INITIAL, - onMessage: fromQml + } 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 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; + } + + var text = message.status; + 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.type === CLARA_IO_DOWNLOAD) { + if (messageBox !== null) { + Window.closeMessageBox(messageBox); + messageBox = null; + } + 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); - 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); - 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; +} + +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 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': + rezEntity(message.itemHref, message.itemType); + 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) { + savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); + } + + 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 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 onMarketplaceScreen = false; +var onWalletScreen = false; +var onCommerceScreen = false; +var onInspectionCertificateScreen = 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 || + onInspectionCertificateScreen); + + // exiting wallet or commerce screen + if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { + maybeEnableHMDPreview(); + } + + onCommerceScreen = onCommerceScreenNow; + onWalletScreen = onWalletScreenNow; + wireQmlEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + + if (url === MARKETPLACE_PURCHASES_QML_PATH) { + 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(); } + console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + + "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n"); +}; - // - // Run the functions. - // - startup(); - Script.scriptEnding.connect(shutdown); +// +// 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..4593c8c2de 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -713,7 +713,7 @@ function tabletVisibilityChanged() { if (!ui.tablet.tabletShown && ui.isOpen) { ui.close(); } - } +} var UPDATE_INTERVAL_MS = 100; var updateInterval; From 5a1d074e2fdcf3cd12bcf9f95627ba5171e88063 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 12 Sep 2018 16:04:37 -0700 Subject: [PATCH 10/29] Goto now uses AppUi; lint --- scripts/system/commerce/wallet.js | 2 +- scripts/system/help.js | 25 +-- scripts/system/pal.js | 9 +- scripts/system/snapshot.js | 3 +- scripts/system/tablet-goto.js | 247 +++++++++++++----------------- 5 files changed, 131 insertions(+), 155 deletions(-) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 213e008bdc..993ea30c2e 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 diff --git a/scripts/system/help.js b/scripts/system/help.js index 325a2c243b..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/ @@ -13,16 +13,17 @@ /* globals Tablet, Script, HMD, Controller, Menu */ (function () { // BEGIN LOCAL_SCOPE - var AppUi = Script.require('appUi'); +var AppUi = Script.require('appUi'); - var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; - var HELP_BUTTON_NAME = "HELP"; - function startup() { - ui = new AppUi({ - buttonName: HELP_BUTTON_NAME, - sortOrder: 6, - home: HELP_URL - }); - } - startup(); +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 + }); +} +startup(); }()); // END LOCAL_SCOPE diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 4593c8c2de..5e38624b35 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 diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index fe952ab1a7..3d744b3bd2 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -7,7 +7,8 @@ // Distributed under the Apache License, Version 2.0 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* 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 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 From 27c7592d986af1424c4e85a7df5d44e2a50b2d7f Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 13 Sep 2018 14:30:42 -0300 Subject: [PATCH 11/29] Patch qt 5.11.1 to support voice_communication opensl es preset --- android/build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index a6de0d469c..a5d40b9b43 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -73,16 +73,16 @@ 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 qtChecksum='bf9e734d9c4e77c4807a38c93515e25c' +def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf' 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='5f12fd4fb25efe648c2fd161fd44998a' + qtVersionId='XXJ7ii1.xYzgFdWPnU.8mXnCj.q6y0Q5' } 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 = [ From ec2c0226a5aadcf0ab762d463b10a30bc10c05ca Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 13 Sep 2018 16:42:12 -0300 Subject: [PATCH 12/29] Replace qt build version (osx) --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index a5d40b9b43..8a3f4d75b9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -77,8 +77,8 @@ def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c' def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' - qtChecksum='5f12fd4fb25efe648c2fd161fd44998a' - qtVersionId='XXJ7ii1.xYzgFdWPnU.8mXnCj.q6y0Q5' + qtChecksum='c83cc477c08a892e00c71764dca051a0' + qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' qtChecksum='0582191cc55431aa4f660848a542883e' From 1215ddbe88a6669173c7a635be0f0b8514e36fce Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 13 Sep 2018 18:50:46 -0300 Subject: [PATCH 13/29] Remove debug files from linux qt build. Remove extra space --- android/build.gradle | 6 +++--- libraries/audio-client/src/AudioClient.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 8a3f4d75b9..14f0779e29 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -72,9 +72,9 @@ 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='bf9e734d9c4e77c4807a38c93515e25c' -def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf' +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_patched.tgz' qtChecksum='c83cc477c08a892e00c71764dca051a0' diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index f0ba0307cc..814cbf87e4 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -454,7 +454,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { Setting::Handle enableAEC(SETTING_AEC_KEY, false); bool aecEnabled = enableAEC.get(); auto audioClient = DependencyManager::get(); - bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false ; + bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false; auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || From 5795ef3c6323c9dbfd5782b486d3687d3016e322 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 13 Sep 2018 19:43:28 -0300 Subject: [PATCH 14/29] Fix qt build (osx) file version --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 14f0779e29..aa7aa399b2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -78,7 +78,7 @@ def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' qtChecksum='c83cc477c08a892e00c71764dca051a0' - qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' + qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' qtChecksum='0582191cc55431aa4f660848a542883e' From 0afea6fbd190721d147e53c17183834239953f9d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 14 Sep 2018 11:08:53 -0700 Subject: [PATCH 15/29] removing double login fail signal --- libraries/networking/src/AccountManager.cpp | 7 ------- libraries/networking/src/AccountManager.h | 1 - 2 files changed, 8 deletions(-) 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(); From 9c5544af393ac8a45f9a527af51ffc51dc15fa88 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 14 Sep 2018 14:37:04 -0700 Subject: [PATCH 16/29] Merge in big work from appui_notifications branch --- .../icons/tablet-icons/people-a-msg.svg | 83 +++++++++++ .../icons/tablet-icons/people-i-msg.svg | 24 ++++ interface/resources/qml/hifi/Pal.qml | 23 ++- scripts/modules/appUi.js | 132 +++++++++++++++--- scripts/system/pal.js | 62 +++++++- 5 files changed, 299 insertions(+), 25 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/people-a-msg.svg create mode 100644 interface/resources/icons/tablet-icons/people-i-msg.svg 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 @@ + + + +image/svg+xml + + \ 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/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/scripts/modules/appUi.js b/scripts/modules/appUi.js index 3a70a69d4d..02beb41674 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/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). @@ -80,13 +81,6 @@ function AppUi(properties) { that.buttonActive = function buttonActive(isActive) { // How to make the button active (white). that.button.editProperties({isActive: isActive}); }; - 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({ - icon: isWaiting ? that.normalMessagesButton : that.normalButton, - activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton - }); - }; that.isQMLUrl = function isQMLUrl(url) { var type = /.qml$/.test(url) ? 'QML' : 'Web'; return type === 'QML'; @@ -95,6 +89,32 @@ function AppUi(properties) { 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({ + icon: isWaiting ? that.normalMessagesButton : that.normalButton, + activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton + }); + }; + that.notificationPollTimeout = false; + that.notificationPollTimeoutMs = 60000; + that.notificationPollEndpoint = false; + that.notificationPollStopPaginatingConditionMet = false; + that.notificationDataProcessPage = function (data) { + return data; + }; + that.notificationPollCallback = that.ignore; + that.notificationPollCaresAboutSince = 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, @@ -126,6 +146,75 @@ function AppUi(properties) { // 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); + 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. @@ -147,8 +236,9 @@ function AppUi(properties) { } that.button = that.tablet.addButton(buttonOptions); that.ignore = function ignore() { }; - that.hasQmlEventBridge = false; - that.hasHtmlEventBridge = 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) { @@ -167,8 +257,10 @@ function AppUi(properties) { // Outbound (always, regardless of whether there is an inbound handler). if (on) { that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml; + that.hasOutboundEventBridge = true; } else { that.sendMessage = that.ignore; + that.hasOutboundEventBridge = false; } if (!that.onMessage) { @@ -177,25 +269,25 @@ function AppUi(properties) { // Inbound if (on) { - if (isCurrentlyOnQMLScreen && !that.hasQmlEventBridge) { + if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) { console.debug(that.buttonName, 'connecting', that.tablet.fromQml); that.tablet.fromQml.connect(that.onMessage); - that.hasQmlEventBridge = true; - } else if (!isCurrentlyOnQMLScreen && !that.hasHtmlEventBridge) { + that.hasInboundQmlEventBridge = true; + } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) { console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived); that.tablet.webEventReceived.connect(that.fromHtml); - that.hasHtmlEventBridge = true; + that.hasInboundHtmlEventBridge = true; } } else { - if (that.hasQmlEventBridge) { + if (that.hasInboundQmlEventBridge) { console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml); that.tablet.fromQml.disconnect(that.onMessage); - that.hasQmlEventBridge = false; + that.hasInboundQmlEventBridge = false; } - if (that.hasHtmlEventBridge) { + if (that.hasInboundHtmlEventBridge) { console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived); that.tablet.webEventReceived.disconnect(that.fromHtml); - that.hasHtmlEventBridge = false; + that.hasInboundHtmlEventBridge = false; } } }; @@ -212,6 +304,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(); } @@ -222,10 +315,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/pal.js b/scripts/system/pal.js index 5e38624b35..1abac53f50 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -321,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)); } @@ -364,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 { @@ -728,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; @@ -810,14 +818,56 @@ function avatarSessionChanged(avatarID) { sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] }); } +function notificationDataProcessPage(data) { + return data.data.users; +} + +var shouldShowDot = false; +var firstBannerNotificationShown = false; +function notificationPollCallback(onlineUsersArray) { + shouldShowDot = onlineUsersArray.length > 0; + + if (!ui.isOpen) { + ui.messagesWaiting(shouldShowDot); + ui.sendMessage({ + method: 'changeConnectionsDotStatus', + shouldShowDot: shouldShowDot + }); + + var message; + if (!firstBannerNotificationShown) { + message = onlineUsersArray.length + " of your connections are online. Open PEOPLE to join them!"; + ui.notificationDisplayBanner(message); + firstBannerNotificationShown = true; + } else { + for (var i = 0; i < onlineUsersArray.length; i++) { + message = onlineUsersArray[i].username + " is available in " + + onlineUsersArray[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&status=online&per_page=10", + notificationPollTimeoutMs: 60000, + notificationDataProcessPage: notificationDataProcessPage, + notificationPollCallback: notificationPollCallback, + notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, + notificationPollCaresAboutSince: true }); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); From b0cde2afcc40c94e10ef1b683af320fec6994947 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 14 Sep 2018 15:05:17 -0700 Subject: [PATCH 17/29] Small addition to Anim Stats * During a state machine interp the previous state now shows up in AlphaValues panel, with parentheses around the name. * Added 3 new fields * Position - in world coordinate frame. * Heading - the facing angle in the world corrdinate frame. * Local Vel - the velocity of the character in the local coordinate frame. (left, forward, up). --- interface/resources/qml/AnimStats.qml | 11 ++++++++-- interface/src/ui/AnimStats.cpp | 23 ++++++++++++++++++++ interface/src/ui/AnimStats.h | 20 ++++++++++++++--- libraries/animation/src/AnimStateMachine.cpp | 4 ++++ libraries/animation/src/AnimVariant.cpp | 9 ++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index 35ed3799a6..d000f3d9be 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -47,6 +47,9 @@ Item { id: firstCol spacing: 4; x: 4; y: 4; + StatText { + text: root.positionText + } StatText { text: "State Machines:---------------------------------------------------------------------------" } @@ -73,10 +76,12 @@ Item { id: secondCol spacing: 4; x: 4; y: 4; + StatText { + text: root.rotationText + } StatText { text: "Anim Vars:--------------------------------------------------------------------------------" } - ListView { width: secondCol.width height: root.animVars.length * 15 @@ -113,10 +118,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/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/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)); } From 11fe657c0588943a4d275f66923cf78c6237fbd2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 17 Sep 2018 10:33:04 -0700 Subject: [PATCH 18/29] Swaped AnimVar and StatMachine panels --- interface/resources/qml/AnimStats.qml | 59 ++++++++++++++------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index d000f3d9be..91ed0eb69b 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -50,35 +50,6 @@ Item { StatText { text: root.positionText } - StatText { - text: "State Machines:---------------------------------------------------------------------------" - } - 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: root.rotationText - } StatText { text: "Anim Vars:--------------------------------------------------------------------------------" } @@ -109,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.animStateMchines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + return modelData; + } + } + } + + } + } + Rectangle { width: thirdCol.width + 8 height: thirdCol.height + 8 From 143a059b16cb5c429a352fb58c0d8aa352986b0a Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 17 Sep 2018 11:11:36 -0700 Subject: [PATCH 19/29] Small changes to AppUI and PAL --- scripts/modules/appUi.js | 7 +++---- scripts/system/pal.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 02beb41674..dab377911b 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -53,10 +53,7 @@ function AppUi(properties) { }; that.open = function open(optionalUrl, optionalInject) { // How to open the app. var url = optionalUrl || that.home; - var inject = that.inject; - if (optionalUrl && optionalInject) { - inject = optionalInject; - } + var inject = optionalInject || that.inject; if (that.isQMLUrl(url)) { that.tablet.loadQMLSource(url); @@ -108,6 +105,7 @@ function AppUi(properties) { }; that.notificationPollCallback = that.ignore; that.notificationPollCaresAboutSince = false; + that.notificationInitialCallbackMade = false; that.notificationDisplayBanner = function (message) { Window.displayAnnouncement(message); }; @@ -189,6 +187,7 @@ function AppUi(properties) { } console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData)); that.notificationPollCallback(notificationData); + that.notificationInitialCallbackMade = true; currentDataPageToRetrieve = 1; concatenatedServerResponse = new Array(); } else { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 1abac53f50..d9e99272ba 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -823,7 +823,6 @@ function notificationDataProcessPage(data) { } var shouldShowDot = false; -var firstBannerNotificationShown = false; function notificationPollCallback(onlineUsersArray) { shouldShowDot = onlineUsersArray.length > 0; @@ -835,10 +834,10 @@ function notificationPollCallback(onlineUsersArray) { }); var message; - if (!firstBannerNotificationShown) { - message = onlineUsersArray.length + " of your connections are online. Open PEOPLE to join them!"; + if (!ui.notificationInitialCallbackMade) { + message = onlineUsersArray.length + " of your connections " + + (onlineUsersArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!"; ui.notificationDisplayBanner(message); - firstBannerNotificationShown = true; } else { for (var i = 0; i < onlineUsersArray.length; i++) { message = onlineUsersArray[i].username + " is available in " + From c453594eb741f93cd3b146dfed961887d88e6641 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 17 Sep 2018 14:41:18 -0700 Subject: [PATCH 20/29] Overhaul notification logic for PAL --- scripts/system/pal.js | 59 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index d9e99272ba..1be5b44786 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -823,8 +823,49 @@ function notificationDataProcessPage(data) { } var shouldShowDot = false; -function notificationPollCallback(onlineUsersArray) { - shouldShowDot = onlineUsersArray.length > 0; +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); @@ -835,13 +876,13 @@ function notificationPollCallback(onlineUsersArray) { var message; if (!ui.notificationInitialCallbackMade) { - message = onlineUsersArray.length + " of your connections " + - (onlineUsersArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!"; + message = newlyOnlineConnectionsArray.length + " of your connections " + + (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!"; ui.notificationDisplayBanner(message); } else { - for (var i = 0; i < onlineUsersArray.length; i++) { - message = onlineUsersArray[i].username + " is available in " + - onlineUsersArray[i].location.root.name + ". Open PEOPLE to join them!"; + 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); } } @@ -861,12 +902,12 @@ function startup() { onOpened: palOpened, onClosed: off, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/users?filter=connections&status=online&per_page=10", + notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10", notificationPollTimeoutMs: 60000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, - notificationPollCaresAboutSince: true + notificationPollCaresAboutSince: false }); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); From 8229c14c4486c57e963f470dd9f0c54a56950d41 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 17 Sep 2018 15:17:43 -0700 Subject: [PATCH 21/29] Fix for type in AnimStats.qml --- interface/resources/qml/AnimStats.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index 91ed0eb69b..b1900cf0a7 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -98,7 +98,7 @@ Item { ListView { width: firstCol.width height: root.animStateMachines.length * 15 - visible: root.animStateMchines.length > 0; + visible: root.animStateMachines.length > 0; model: root.animStateMachines delegate: StatText { text: { From d48791465627801813b4cc67ca71bbdaef769b23 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 11:28:59 -0700 Subject: [PATCH 22/29] Prevent text notification from appearing when not necessary --- scripts/system/pal.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 1be5b44786..85898c28fb 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -874,16 +874,18 @@ function notificationPollCallback(connectionsArray) { shouldShowDot: shouldShowDot }); - 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!"; + 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); + } } } } From 783ea2195147844008740eb37286c5b801f5999f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 18 Sep 2018 16:45:22 -0700 Subject: [PATCH 23/29] Fix AMD highlighting effect --- libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp | 2 +- libraries/render-utils/src/Highlight.slh | 2 +- libraries/render-utils/src/Highlight_aabox.slv | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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/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; }; From 8805ce49e3e1349c4a123cca909c00aaf915e934 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 19 Sep 2018 15:06:58 -0700 Subject: [PATCH 24/29] Remove verbose logging of keyboard messages in edit.js and gridTool.js --- scripts/system/edit.js | 4 +--- scripts/system/libraries/gridTool.js | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) 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/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; } From cf85c617a285825c4e9463bcb2f403f91b7f9930 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 20 Sep 2018 09:56:50 -0700 Subject: [PATCH 25/29] tweak avatar update priority coefs for load tests --- libraries/avatars/src/AvatarData.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) { From 8bd698b143d7dcb625d56926618ce4813807764c Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 20 Sep 2018 11:13:27 -0700 Subject: [PATCH 26/29] leaving button in natural dims --- scripts/system/interstitialPage.js | 1 - 1 file changed, 1 deletion(-) 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, From 72adfb7ff09ea4f708ff1edae0f3062e85882f4a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 18 Sep 2018 11:08:40 -0700 Subject: [PATCH 27/29] Fix entity list selection failing when selecting entities not in list --- scripts/system/html/js/entityList.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 9cfdf6df22..0e025ef4c4 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -161,7 +161,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 = ''; + } } }); @@ -385,15 +388,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; From 1ee655c8f86e6343830b967c4bf997aff7ccd81d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 18 Sep 2018 15:14:08 -0700 Subject: [PATCH 28/29] Fix sorting of entity list not working as expected --- scripts/system/html/js/entityList.js | 41 +++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 9cfdf6df22..23de49b613 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); @@ -223,15 +226,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 +262,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); From d772cdb34a1491196381752b682dcffbabdb7a1f Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 24 Sep 2018 10:16:35 -0700 Subject: [PATCH 29/29] please --- libraries/render-utils/src/Model.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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));