From 11bfc1ca74e3dbc02e926500228d19d9de2d82a1 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 21 Mar 2018 17:40:14 -0300 Subject: [PATCH 01/83] Add Goto as splash screen in android --- android/app/build.gradle | 7 ++ android/app/src/main/AndroidManifest.xml | 9 ++ .../hifiinterface/GotoActivity.java | 111 ++++++++++++++++++ .../hifiinterface/InterfaceActivity.java | 10 +- .../hifiinterface/PermissionChecker.java | 2 +- .../hifiinterface/view/DomainAdapter.java | 88 ++++++++++++++ .../app/src/main/res/drawable/ic_bookmark.xml | 4 + android/app/src/main/res/drawable/ic_menu.xml | 9 ++ .../app/src/main/res/drawable/ic_person.xml | 5 + .../app/src/main/res/drawable/ic_share.xml | 4 + android/app/src/main/res/font/raleway.ttf | Bin 0 -> 178520 bytes .../src/main/res/font/raleway_semibold.xml | 7 ++ .../app/src/main/res/layout/activity_goto.xml | 38 ++++++ .../app/src/main/res/layout/content_goto.xml | 69 +++++++++++ .../app/src/main/res/layout/domain_view.xml | 76 ++++++++++++ android/app/src/main/res/menu/menu_goto.xml | 10 ++ android/app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/font_certs.xml | 17 +++ .../src/main/res/values/preloaded_fonts.xml | 6 + android/app/src/main/res/values/strings.xml | 5 + android/app/src/main/res/values/styles.xml | 18 +-- interface/src/Application.cpp | 7 +- libraries/networking/src/AddressManager.cpp | 2 - 23 files changed, 495 insertions(+), 15 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java create mode 100644 android/app/src/main/res/drawable/ic_bookmark.xml create mode 100644 android/app/src/main/res/drawable/ic_menu.xml create mode 100644 android/app/src/main/res/drawable/ic_person.xml create mode 100644 android/app/src/main/res/drawable/ic_share.xml create mode 100644 android/app/src/main/res/font/raleway.ttf create mode 100644 android/app/src/main/res/font/raleway_semibold.xml create mode 100644 android/app/src/main/res/layout/activity_goto.xml create mode 100644 android/app/src/main/res/layout/content_goto.xml create mode 100644 android/app/src/main/res/layout/domain_view.xml create mode 100644 android/app/src/main/res/menu/menu_goto.xml create mode 100644 android/app/src/main/res/values/font_certs.xml create mode 100644 android/app/src/main/res/values/preloaded_fonts.xml diff --git a/android/app/build.gradle b/android/app/build.gradle index 97267258e2..7a6f34b58b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,6 +98,13 @@ android { dependencies { implementation 'com.google.vr:sdk-audio:1.80.0' implementation 'com.google.vr:sdk-base:1.80.0' + + + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support:design:26.1.0' + implementation 'com.android.support:appcompat-v7:26.1.0' + compile 'com.android.support:recyclerview-v7:26.1.0' + implementation fileTree(include: ['*.jar'], dir: 'libs') } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b3a8c87649..f626334dbd 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -38,6 +38,11 @@ --> + + + + diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java new file mode 100644 index 0000000000..978ce80573 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -0,0 +1,111 @@ +package io.highfidelity.hifiinterface; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TextView; +import android.widget.Toast; + +import io.highfidelity.hifiinterface.view.DomainAdapter; + +public class GotoActivity extends AppCompatActivity { + + private DomainAdapter domainAdapter; + private DrawerLayout mDrawerLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_goto); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + ActionBar actionbar = getSupportActionBar(); + actionbar.setDisplayHomeAsUpEnabled(true); + actionbar.setHomeAsUpIndicator(R.drawable.ic_menu); + + mDrawerLayout = findViewById(R.id.drawer_layout); + + TabHost tabs=(TabHost)findViewById(R.id.tabhost); + tabs.setup(); + + TabHost.TabSpec spec=tabs.newTabSpec("featured"); + spec.setContent(R.id.featured); + spec.setIndicator(getString(R.string.featured)); + tabs.addTab(spec); + + spec=tabs.newTabSpec("popular"); + spec.setContent(R.id.popular); + spec.setIndicator(getString(R.string.popular)); + tabs.addTab(spec); + + spec=tabs.newTabSpec("bookmarks"); + spec.setContent(R.id.bookmarks); + spec.setIndicator(getString(R.string.bookmarks)); + tabs.addTab(spec); + + tabs.setCurrentTab(0); + + TabWidget tabwidget=tabs.getTabWidget(); + for(int i=0;i { + + private Context mContext; + private LayoutInflater mInflater; + private ItemClickListener mClickListener; + + public DomainAdapter(Context c) { + mContext = c; + this.mInflater = LayoutInflater.from(mContext); + } + + public class Domain { + public String name; + public String url; + public Integer thumbnail; + Domain(String name, String url, Integer thumbnail) { + this.name = name; + this.thumbnail = thumbnail; + this.url = url; + } + } + + // references to our domains + private Domain[] mDomains = { + new Domain("Dev-master-mobile", "hifi://Dev-master-mobile", 0), + new Domain("Pikachu", "hifi://pikachu", 0), + }; + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.domain_view, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + // TODO + //holder.thumbnail.setImageResource(mDomains[position].thumbnail); + holder.mDomainName.setText(mDomains[position].name); + } + + @Override + public int getItemCount() { + return mDomains.length; + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + TextView mDomainName; + ImageView mThumbnail; + + ViewHolder(View itemView) { + super(itemView); + mThumbnail = (ImageView) itemView.findViewById(R.id.domainThumbnail); + mDomainName = (TextView) itemView.findViewById(R.id.domainName); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + int position = getAdapterPosition(); + if (mClickListener != null) mClickListener.onItemClick(view, position, mDomains[position]); + } + } + + // allows clicks events to be caught + public void setClickListener(ItemClickListener itemClickListener) { + this.mClickListener = itemClickListener; + } + // parent activity will implement this method to respond to click events + public interface ItemClickListener { + void onItemClick(View view, int position, Domain domain); + } +} diff --git a/android/app/src/main/res/drawable/ic_bookmark.xml b/android/app/src/main/res/drawable/ic_bookmark.xml new file mode 100644 index 0000000000..ddf03e340b --- /dev/null +++ b/android/app/src/main/res/drawable/ic_bookmark.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_menu.xml b/android/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000000..cf37e2a393 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_person.xml b/android/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 0000000000..cf57059c77 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_share.xml b/android/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 0000000000..91b01694da --- /dev/null +++ b/android/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/font/raleway.ttf b/android/app/src/main/res/font/raleway.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e570a2d5c39b2841b5753350a3822bd2b685e771 GIT binary patch literal 178520 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 + + diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml new file mode 100644 index 0000000000..ab0fd69a74 --- /dev/null +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/content_goto.xml b/android/app/src/main/res/layout/content_goto.xml new file mode 100644 index 0000000000..ee8acc8748 --- /dev/null +++ b/android/app/src/main/res/layout/content_goto.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/domain_view.xml b/android/app/src/main/res/layout/domain_view.xml new file mode 100644 index 0000000000..47028856d6 --- /dev/null +++ b/android/app/src/main/res/layout/domain_view.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/menu_goto.xml b/android/app/src/main/res/menu/menu_goto.xml new file mode 100644 index 0000000000..41bbf6452e --- /dev/null +++ b/android/app/src/main/res/menu/menu_goto.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 344907f039..9ed5686d3c 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,10 @@ #ffffff + #272727 + #303F9F + #54D7FD + #1EB5EC + #333333 + #4F4F4F diff --git a/android/app/src/main/res/values/font_certs.xml b/android/app/src/main/res/values/font_certs.xml new file mode 100644 index 0000000000..d2226ac01c --- /dev/null +++ b/android/app/src/main/res/values/font_certs.xml @@ -0,0 +1,17 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + 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 + + + diff --git a/android/app/src/main/res/values/preloaded_fonts.xml b/android/app/src/main/res/values/preloaded_fonts.xml new file mode 100644 index 0000000000..82bcb6c19f --- /dev/null +++ b/android/app/src/main/res/values/preloaded_fonts.xml @@ -0,0 +1,6 @@ + + + + @font/raleway_semibold + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b8080fae0f..f618fe805d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,8 +1,13 @@ Interface + Go To Open in browser Share link Shared a link Share link + FEATURED + POPULAR + BOOKMARKS + Settings diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 23fe67f029..31fcbd8350 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,16 +1,20 @@ - + + + + + + + + \ No newline at end of file + + + + + + + + + + + diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android/myview-a.svg index 307b559c95..9964678074 100755 --- a/interface/resources/icons/+android/myview-a.svg +++ b/interface/resources/icons/+android/myview-a.svg @@ -1,56 +1,19 @@ - - - -image/svg+xmlAsset 3 \ No newline at end of file + + + + + + + + + + + diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml new file mode 100644 index 0000000000..7152c829bc --- /dev/null +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: actionBar + x:0 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + actionBar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + // put on bottom + x = 50; + y = 0; + width = 300; + height = 300; + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index f524595ef5..a6d4e28813 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -40,7 +40,7 @@ Item { Component.onCompleted: { // put on bottom - x = 0; + x = parent.width-300; y = 0; width = 300; height = 300; diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index fe71314ece..451921f155 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -10,7 +10,7 @@ import ".." Item { id: modesbar - y:60 + y:20 Rectangle { anchors.fill : parent color: "transparent" @@ -25,9 +25,9 @@ Item { } Component.onCompleted: { - width = 300 + 30; // That 30 is extra regardless the qty of items shown - height = 300 + 30; - x=Window.innerWidth - width; + width = 300; // That 30 is extra regardless the qty of items shown + height = 300; + x=parent.width - 540; } function addButton(properties) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e943157954..80775e1930 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -242,6 +242,7 @@ extern "C" { #if defined(Q_OS_ANDROID) #include +#include #endif enum ApplicationEvent { @@ -7870,7 +7871,8 @@ void Application::saveNextPhysicsStats(QString filename) { void Application::openAndroidActivity(const QString& activityName) { #if defined(Q_OS_ANDROID) - getActiveDisplayPlugin()->deactivate(); + qDebug() << "[Background-HIFI] Application::openAndroidActivity"; + //getActiveDisplayPlugin()->deactivate(); AndroidHelper::instance().requestActivity(activityName); connect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity); #endif @@ -7878,11 +7880,63 @@ void Application::openAndroidActivity(const QString& activityName) { void Application::restoreAfterAndroidActivity() { #if defined(Q_OS_ANDROID) - if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { + qDebug() << "[Background-HIFI] restoreAfterAndroidActivity: this wouldn't be needed"; + + /*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { qWarning() << "Could not re-activate display plugin"; - } + }*/ disconnect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity); #endif } +#if defined(Q_OS_ANDROID) +void Application::enterBackground() { + qDebug() << "[Background-HIFI] enterBackground begin"; + QMetaObject::invokeMethod(DependencyManager::get().data(), + "stop", Qt::BlockingQueuedConnection); + qDebug() << "[Background-HIFI] deactivating display plugin"; + getActiveDisplayPlugin()->deactivate(); + qDebug() << "[Background-HIFI] enterBackground end"; +} +void Application::enterForeground() { + qDebug() << "[Background-HIFI] enterForeground qApp?" << (qApp?"yeah":"false"); + if (qApp && DependencyManager::isSet()) { + qDebug() << "[Background-HIFI] audioclient.start()"; + QMetaObject::invokeMethod(DependencyManager::get().data(), + "start", Qt::BlockingQueuedConnection); + } else { + qDebug() << "[Background-HIFI] audioclient.start() not done"; + } + if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { + qWarning() << "[Background-HIFI] Could not re-activate display plugin"; + } + +} + +extern "C" { + + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) { + qDebug() << "[Background-HIFI] nativeEnterBackground"; + if (qApp) { + qDebug() << "[Background-HIFI] nativeEnterBackground begin (qApp)"; + qApp->enterBackground(); + } +} + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) { + qDebug() << "[Background-HIFI] nativeEnterForeground"; + if (qApp) { + qDebug() << "[Background-HIFI] nativeEnterForeground begin (qApp)"; + qApp->enterForeground(); + } +} + + +} + +#endif + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index ab55861930..ec49aa055f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -292,6 +292,11 @@ public: void replaceDomainContent(const QString& url); +#if defined(Q_OS_ANDROID) + void enterBackground(); + void enterForeground(); +#endif + signals: void svoImportRequested(const QString& url); diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index 11aee6a9d2..98fbb4b1a7 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -14,7 +14,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", "system/+android/touchscreenvirtualpad.js", - "system/+android/bottombar.js", + "system/+android/actionbar.js", "system/+android/audio.js" , "system/+android/modes.js", "system/+android/stats.js"/*, diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android/actionbar.js new file mode 100644 index 0000000000..9aed1e6cf9 --- /dev/null +++ b/scripts/system/+android/actionbar.js @@ -0,0 +1,53 @@ +"use strict"; +// +// backbutton.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Apr 06, 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function() { // BEGIN LOCAL_SCOPE + +var actionbar; +var backButton; + +var logEnabled = true; + +function printd(str) { + if (logEnabled) + print("[actionbar.js] " + str); +} + +function init() { + actionbar = new QmlFragment({ + qml: "hifi/ActionBar.qml" + }); + backButton = actionbar.addButton({ + icon: "icons/+android/backward.svg", + activeIcon: "icons/+android/backward.svg", + text: "", + bgOpacity: 0.0, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF" + }); + + backButton.clicked.connect(onBackPressed); +} + +function onBackPressed() { + App.openAndroidActivity("Goto"); +} + + +Script.scriptEnding.connect(function () { + if(backButton) { + backButton.clicked.disconnect(onBackPressed); + } +}); + +init(); + +}()); // END LOCAL_SCOPE From 8010fd2420f5e05dce90b543a23db9fb3262a17a Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 11 Apr 2018 18:43:39 -0300 Subject: [PATCH 12/83] New view selector behaviour. Update icons --- .../resources/icons/+android/backward.svg | 10 +- .../resources/icons/+android/mic-unmute-a.svg | 92 +++-------- .../resources/icons/+android/myview-a.svg | 12 +- .../resources/icons/+android/radar-a.svg | 1 - .../resources/icons/+android/radar-hover.svg | 1 - .../resources/icons/+android/radar-i.svg | 1 - interface/resources/images/fly.png | Bin 11088 -> 8688 bytes .../resources/qml/hifi/+android/modesbar.qml | 26 +-- scripts/system/+android/modes.js | 156 ++++-------------- 9 files changed, 74 insertions(+), 225 deletions(-) mode change 100644 => 100755 interface/resources/icons/+android/mic-unmute-a.svg delete mode 100755 interface/resources/icons/+android/radar-a.svg delete mode 100755 interface/resources/icons/+android/radar-hover.svg delete mode 100755 interface/resources/icons/+android/radar-i.svg diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android/backward.svg index ad102b886e..6b4c560768 100755 --- a/interface/resources/icons/+android/backward.svg +++ b/interface/resources/icons/+android/backward.svg @@ -3,13 +3,11 @@ - - - - + diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android/mic-unmute-a.svg old mode 100644 new mode 100755 index bb28dc0f2b..8717636a34 --- a/interface/resources/icons/+android/mic-unmute-a.svg +++ b/interface/resources/icons/+android/mic-unmute-a.svg @@ -1,70 +1,22 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + + + + + + diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android/myview-a.svg index 9964678074..f8becb3850 100755 --- a/interface/resources/icons/+android/myview-a.svg +++ b/interface/resources/icons/+android/myview-a.svg @@ -3,17 +3,17 @@ - - + C49.4,25.9,49.5,24.2,48.1,22.6z M46.4,27.2C41,34.1,33.9,38.1,26.2,38.6c-7.3-0.1-12.6-2.4-17.4-6c-2.2-1.6-4.1-3.5-5.6-5.8 + c-0.7-1-0.9-2-0.1-3.1c4.8-7.1,16.4-14.6,28.2-11.1c6.2,1.9,11.3,5.3,15.2,10.5C47.8,24.8,47.8,25.4,46.4,27.2z"/> + diff --git a/interface/resources/icons/+android/radar-a.svg b/interface/resources/icons/+android/radar-a.svg deleted file mode 100755 index e4b157f827..0000000000 --- a/interface/resources/icons/+android/radar-a.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-hover.svg b/interface/resources/icons/+android/radar-hover.svg deleted file mode 100755 index e4b157f827..0000000000 --- a/interface/resources/icons/+android/radar-hover.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/interface/resources/icons/+android/radar-i.svg b/interface/resources/icons/+android/radar-i.svg deleted file mode 100755 index 3994a775d3..0000000000 --- a/interface/resources/icons/+android/radar-i.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/interface/resources/images/fly.png b/interface/resources/images/fly.png index 0edfcab21bbe3f78c684a004a00d5c7ce7c5a3fe..02f72d568983840d96ee85c8fdc13775fac26919 100644 GIT binary patch literal 8688 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 diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 451921f155..642703017f 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -11,18 +11,6 @@ import ".." Item { id: modesbar y:20 - Rectangle { - anchors.fill : parent - color: "transparent" - Flow { - id: flowMain - spacing: 0 - flow: Flow.TopToBottom - layoutDirection: Flow.TopToBottom - anchors.fill: parent - anchors.margins: 4 - } - } Component.onCompleted: { width = 300; // That 30 is extra regardless the qty of items shown @@ -35,7 +23,7 @@ Item { console.log("load button"); if (component.status == Component.Ready) { console.log("load button 2"); - var button = component.createObject(flowMain); + var button = component.createObject(modesbar); // copy all properites to button var keys = Object.keys(properties).forEach(function (key) { button[key] = properties[key]; @@ -59,14 +47,12 @@ Item { function fromScript(message) { switch (message.type) { - case "allButtonsShown": - modesbar.height = flowMain.children.length * 300 + 30; // That 30 is extra regardless the qty of items shown - break; - case "inactiveButtonsHidden": - modesbar.height = 300 + 30; - break; + case "switch": + // message.params.to + // still not needed + break; default: - break; + break; } } diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index c41ae1f327..a7c1060ffb 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -11,15 +11,21 @@ // (function() { // BEGIN LOCAL_SCOPE -var modesbar; -var modesButtons; -var currentSelectedBtn; +var modeButton; +var currentMode; +var barQml; var SETTING_CURRENT_MODE_KEY = 'Android/Mode'; var MODE_VR = "VR", MODE_RADAR = "RADAR", MODE_MY_VIEW = "MY VIEW"; var DEFAULT_MODE = MODE_MY_VIEW; -var logEnabled = true; +var nextMode = {}; +nextMode[MODE_RADAR]=MODE_MY_VIEW; +nextMode[MODE_MY_VIEW]=MODE_RADAR; +var modeLabel = {}; +modeLabel[MODE_RADAR]="TOP VIEW"; +modeLabel[MODE_MY_VIEW]="MY VIEW"; +var logEnabled = false; var radar = Script.require('./radar.js'); var uniqueColor = Script.require('./uniqueColor.js'); @@ -32,87 +38,30 @@ function printd(str) { function init() { radar.setUniqueColor(uniqueColor); radar.init(); - setupModesBar(); -} - -function shutdown() { - -} - -function setupModesBar() { - - var bar = new QmlFragment({ + + barQml = new QmlFragment({ qml: "hifi/modesbar.qml" }); - var buttonRadarMode = bar.addButton({ - icon: "icons/radar-i.svg", - activeIcon: "icons/radar-a.svg", - hoverIcon: "icons/radar-a.svg", + modeButton = barQml.addButton({ + icon: "icons/myview-a.svg", activeBgOpacity: 0.0, hoverBgOpacity: 0.0, activeHoverBgOpacity: 0.0, - text: "RADAR", + text: "MODE", height:240, bottomMargin: 6, textSize: 45 }); - var buttonMyViewMode = bar.addButton({ - icon: "icons/myview-i.svg", - activeIcon: "icons/myview-a.svg", - hoverIcon: "icons/myview-a.svg", - activeBgOpacity: 0.0, - hoverBgOpacity: 0.0, - activeHoverBgOpacity: 0.0, - text: "MY VIEW", - height: 240, - bottomMargin: 6, - textSize: 45 - }); - modesButtons = [buttonRadarMode, buttonMyViewMode]; + switchToMode(getCurrentModeSetting()); - var mode = getCurrentModeSetting(); - - var buttonsRevealed = false; - bar.sendToQml({type: "inactiveButtonsHidden"}); - - modesbar = { - restoreMyViewButton: function() { - switchModeButtons(buttonMyViewMode); - saveCurrentModeSetting(MODE_MY_VIEW); - }, - sendToQml: function(o) { bar.sendToQml(o); }, - qmlFragment: bar - }; - - buttonRadarMode.clicked.connect(function() { - //if (connections.isVisible()) return; - saveCurrentModeSetting(MODE_RADAR); - printd("Radar clicked"); - onButtonClicked(buttonRadarMode, function() { - radar.startRadarMode(); - }); - }); - buttonMyViewMode.clicked.connect(function() { - //if (connections.isVisible()) return; - saveCurrentModeSetting(MODE_MY_VIEW); - printd("My View clicked"); - onButtonClicked(buttonMyViewMode, function() { - if (currentSelectedBtn == buttonRadarMode) { - radar.endRadarMode(); - } - }); + modeButton.clicked.connect(function() { + switchToMode(nextMode[currentMode]); }); +} - var savedButton; - if (mode == MODE_MY_VIEW) { - savedButton = buttonMyViewMode; - } else { - savedButton = buttonRadarMode; - } - printd("[MODE] previous mode " + mode); +function shutdown() { - savedButton.clicked(); } function saveCurrentModeSetting(mode) { @@ -123,62 +72,29 @@ function getCurrentModeSetting(mode) { return Settings.getValue(SETTING_CURRENT_MODE_KEY, DEFAULT_MODE); } -function showAllButtons() { - for (var i=0; i Date: Wed, 11 Apr 2018 19:33:43 -0300 Subject: [PATCH 13/83] Move jump button to the right. Adjust position of buttons --- android/app/src/main/res/values/strings.xml | 2 +- interface/resources/qml/hifi/+android/ActionBar.qml | 2 +- interface/resources/qml/hifi/+android/AudioBar.qml | 4 ++-- interface/resources/qml/hifi/+android/modesbar.qml | 2 +- .../src/input-plugins/TouchscreenVirtualPadDevice.cpp | 5 +++-- libraries/ui/src/VirtualPadManager.cpp | 4 ++-- libraries/ui/src/VirtualPadManager.h | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f618fe805d..8f2d043f8d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ Interface - Go To + Home Open in browser Share link Shared a link diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml index 7152c829bc..3bc4785e02 100644 --- a/interface/resources/qml/hifi/+android/ActionBar.qml +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -40,7 +40,7 @@ Item { Component.onCompleted: { // put on bottom - x = 50; + x = 30; y = 0; width = 300; height = 300; diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index a6d4e28813..0480d4ee4f 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -40,8 +40,8 @@ Item { Component.onCompleted: { // put on bottom - x = parent.width-300; - y = 0; + x = parent.width-315; + y = 5; width = 300; height = 300; } diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 642703017f..7736c589a1 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -15,7 +15,7 @@ Item { Component.onCompleted: { width = 300; // That 30 is extra regardless the qty of items shown height = 300; - x=parent.width - 540; + x=parent.width - 555; } function addButton(properties) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 957104bd30..0f3002b8c1 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -80,9 +80,10 @@ void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& vi virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); // Jump button - float leftMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_LEFT_MARGIN_PIXELS / VirtualPad::Manager::DPI; + float jumpBtnPixelSize = _screenDPI * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI; + float rightMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_RIGHT_MARGIN_PIXELS / VirtualPad::Manager::DPI; float bottomMargin = _screenDPI * VirtualPad::Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS/ VirtualPad::Manager::DPI; - _jumpButtonPosition = glm::vec2( _jumpButtonRadius + leftMargin, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin); + _jumpButtonPosition = glm::vec2( eventScreen->size().width() - rightMargin - jumpBtnPixelSize, eventScreen->size().height() - bottomMargin - _jumpButtonRadius - _extraBottomMargin); virtualPadManager.setJumpButtonPosition(_jumpButtonPosition); } diff --git a/libraries/ui/src/VirtualPadManager.cpp b/libraries/ui/src/VirtualPadManager.cpp index c786110bdf..1c76672827 100644 --- a/libraries/ui/src/VirtualPadManager.cpp +++ b/libraries/ui/src/VirtualPadManager.cpp @@ -39,9 +39,9 @@ namespace VirtualPad { const float Manager::BASE_MARGIN_PIXELS = 59.0f; const float Manager::STICK_RADIUS_PIXELS = 105.0f; const float Manager::JUMP_BTN_TRIMMED_RADIUS_PIXELS = 67.0f; - const float Manager::JUMP_BTN_FULL_PIXELS = 134.0f; + const float Manager::JUMP_BTN_FULL_PIXELS = 164.0f; const float Manager::JUMP_BTN_BOTTOM_MARGIN_PIXELS = 67.0f; - const float Manager::JUMP_BTN_LEFT_MARGIN_PIXELS = 547.0f; + const float Manager::JUMP_BTN_RIGHT_MARGIN_PIXELS = 20.0f; Manager::Manager() { diff --git a/libraries/ui/src/VirtualPadManager.h b/libraries/ui/src/VirtualPadManager.h index 68b3d4f10f..6f7fbcc921 100644 --- a/libraries/ui/src/VirtualPadManager.h +++ b/libraries/ui/src/VirtualPadManager.h @@ -54,7 +54,7 @@ namespace VirtualPad { static const float JUMP_BTN_TRIMMED_RADIUS_PIXELS; static const float JUMP_BTN_FULL_PIXELS; static const float JUMP_BTN_BOTTOM_MARGIN_PIXELS; - static const float JUMP_BTN_LEFT_MARGIN_PIXELS; + static const float JUMP_BTN_RIGHT_MARGIN_PIXELS; private: Instance _leftVPadInstance; From 5bf894d1f7fbfc8b80c28842862ef7303f25a2b9 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 Apr 2018 16:48:02 -0700 Subject: [PATCH 14/83] Fix FBX vertex colors not being read correctly --- libraries/fbx/src/FBXReader_Mesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index b0e2faa600..4b9b8d0fd6 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -249,7 +249,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn indexToDirect = true; } } - if (indexToDirect && data.normalIndices.isEmpty()) { + if (indexToDirect && data.colorIndices.isEmpty()) { // hack to work around wacky Makehuman exports data.colorsByVertex = true; } From a7328f09347785d68914602e20a0d858053d7381 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 12 Apr 2018 15:55:20 -0300 Subject: [PATCH 15/83] Fix View not attached to windown manager crash when starting the app in landscape mode on Pixel XL --- .../java/io/highfidelity/hifiinterface/GotoActivity.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index dc07c7b99a..4265201946 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -174,4 +174,10 @@ public class GotoActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } + + @Override + protected void onDestroy() { + cancelActivityIndicator(); + super.onDestroy(); + } } From 0f39ab0bb92eadef009e2f293238ed25d4614590 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 12 Apr 2018 17:47:58 -0300 Subject: [PATCH 16/83] Android GotoAcitivity is now HomeActivity (leaving the place for the upcoming 'Go To activity' itself --- android/app/src/main/AndroidManifest.xml | 2 +- .../{GotoActivity.java => HomeActivity.java} | 14 +++++++------- .../hifiinterface/InterfaceActivity.java | 4 ++-- .../hifiinterface/PermissionChecker.java | 4 ++-- .../{activity_goto.xml => activity_home.xml} | 4 ++-- .../layout/{content_goto.xml => content_home.xml} | 4 ++-- .../main/res/menu/{menu_goto.xml => menu_home.xml} | 7 ++++++- android/app/src/main/res/values/strings.xml | 1 + android/app/src/main/res/values/styles.xml | 2 +- 9 files changed, 24 insertions(+), 18 deletions(-) rename android/app/src/main/java/io/highfidelity/hifiinterface/{GotoActivity.java => HomeActivity.java} (94%) rename android/app/src/main/res/layout/{activity_goto.xml => activity_home.xml} (93%) rename android/app/src/main/res/layout/{content_goto.xml => content_home.xml} (96%) rename android/app/src/main/res/menu/{menu_goto.xml => menu_home.xml} (60%) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index caea9c2939..c77caa20fb 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -39,7 +39,7 @@ --> diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java similarity index 94% rename from android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java rename to android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index 4265201946..d69faec2e3 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -23,7 +23,7 @@ import android.widget.TextView; import io.highfidelity.hifiinterface.QtPreloader.QtPreloader; import io.highfidelity.hifiinterface.view.DomainAdapter; -public class GotoActivity extends AppCompatActivity { +public class HomeActivity extends AppCompatActivity { /** * Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected" @@ -36,9 +36,9 @@ public class GotoActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_goto); + setContentView(R.layout.activity_home); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - toolbar.setTitleTextAppearance(this, R.style.GotoActionBarTitleStyle); + toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle); setSupportActionBar(toolbar); ActionBar actionbar = getSupportActionBar(); @@ -83,9 +83,9 @@ public class GotoActivity extends AppCompatActivity { @Override public void onItemClick(View view, int position, DomainAdapter.Domain domain) { - Intent intent = new Intent(GotoActivity.this, InterfaceActivity.class); + Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); intent.putExtra(InterfaceActivity.DOMAIN_URL, domain.url); - GotoActivity.this.finish(); + HomeActivity.this.finish(); if (getIntent() != null && getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) && getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { @@ -137,7 +137,7 @@ public class GotoActivity extends AppCompatActivity { preloadTask = new AsyncTask() { @Override protected Object doInBackground(Object[] objects) { - new QtPreloader(GotoActivity.this).initQt(); + new QtPreloader(HomeActivity.this).initQt(); runOnUiThread(new Runnable() { @Override public void run() { @@ -154,7 +154,7 @@ public class GotoActivity extends AppCompatActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. - //getMenuInflater().inflate(R.menu.menu_goto, menu); + //getMenuInflater().inflate(R.menu.menu_home, menu); return true; } 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 812252663a..678f7e8aac 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -200,8 +200,8 @@ public class InterfaceActivity extends QtActivity { public void openGotoActivity(String activityName) { switch (activityName) { case "Goto": { - Intent intent = new Intent(this, GotoActivity.class); - intent.putExtra(GotoActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true); + Intent intent = new Intent(this, HomeActivity.class); + intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true); startActivity(intent); break; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java index e52000f944..b1c5f570c8 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -11,7 +11,7 @@ import android.app.AlertDialog; import org.json.JSONException; import org.json.JSONObject; -import android.util.Log; + import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; @@ -64,7 +64,7 @@ public class PermissionChecker extends Activity { private void launchActivityWithPermissions(){ finish(); - Intent i = new Intent(this, GotoActivity.class); + Intent i = new Intent(this, HomeActivity.class); startActivity(i); finish(); } diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_home.xml similarity index 93% rename from android/app/src/main/res/layout/activity_goto.xml rename to android/app/src/main/res/layout/activity_home.xml index ab0fd69a74..144ca84a0f 100644 --- a/android/app/src/main/res/layout/activity_goto.xml +++ b/android/app/src/main/res/layout/activity_home.xml @@ -21,7 +21,7 @@ android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> - + @@ -32,7 +32,7 @@ android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" - app:menu="@menu/menu_goto" + app:menu="@menu/menu_home" /> diff --git a/android/app/src/main/res/layout/content_goto.xml b/android/app/src/main/res/layout/content_home.xml similarity index 96% rename from android/app/src/main/res/layout/content_goto.xml rename to android/app/src/main/res/layout/content_home.xml index 1e17fff07c..f25d9d8f7b 100644 --- a/android/app/src/main/res/layout/content_goto.xml +++ b/android/app/src/main/res/layout/content_home.xml @@ -5,8 +5,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" - tools:context="io.highfidelity.hifiinterface.GotoActivity" - tools:showIn="@layout/activity_goto"> + tools:context="io.highfidelity.hifiinterface.HomeActivity" + tools:showIn="@layout/activity_home"> + tools:context="io.highfidelity.hifiinterface.HomeActivity"> + POPULAR BOOKMARKS Settings + Go to diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cfa040837d..2058212651 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -16,7 +16,7 @@ - + - - - + + + diff --git a/interface/resources/images/fly.png b/interface/resources/images/fly.png index 02f72d568983840d96ee85c8fdc13775fac26919..b8b96645f8880f53c27505d4a3ee95d58d5ca337 100644 GIT binary patch literal 8137 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 literal 8688 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 Date: Thu, 12 Apr 2018 19:02:03 -0300 Subject: [PATCH 18/83] Hide jump button while moving. Fix jump button right alignment in pixel --- interface/src/Application.cpp | 3 ++ .../Basic2DWindowOpenGLDisplayPlugin.cpp | 29 ++++++++++--------- .../TouchscreenVirtualPadDevice.cpp | 18 ++++++++---- .../TouchscreenVirtualPadDevice.h | 1 + 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0b68263ba4..8cba6b1220 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2626,6 +2626,9 @@ void Application::initializeUi() { offscreenUi->resume(); connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ resizeGL(); + if (_touchscreenVirtualPadDevice) { + _touchscreenVirtualPadDevice->resize(); + } }); // This will set up the input plugins UI diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index f33af1b580..59cd637ca0 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -147,20 +147,21 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); }); - - // render stick head - auto jumpTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(), - _virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize); - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setProjectionTransform(mat4()); - batch.setPipeline(_cursorPipeline); - batch.setResourceTexture(0, _virtualPadJumpBtnTexture); - batch.resetViewTransform(); - batch.setModelTransform(jumpTransform); - batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); + if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) { + // render stick head + auto jumpTransform = DependencyManager::get()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(), + _virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize); + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setProjectionTransform(mat4()); + batch.setPipeline(_cursorPipeline); + batch.setResourceTexture(0, _virtualPadJumpBtnTexture); + batch.resetViewTransform(); + batch.setModelTransform(jumpTransform); + batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize())); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } } #endif Parent::compositeExtra(); diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 0f3002b8c1..7ee2135325 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -43,6 +43,18 @@ void TouchscreenVirtualPadDevice::init() { _fixedPosition = true; // This should be config _viewTouchUpdateCount = 0; + resize(); + + auto& virtualPadManager = VirtualPad::Manager::instance(); + + if (_fixedPosition) { + virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled + } + + KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin +} + +void TouchscreenVirtualPadDevice::resize() { QScreen* eventScreen = qApp->primaryScreen(); if (_screenDPIProvided != eventScreen->physicalDotsPerInch()) { _screenWidthCenter = eventScreen->size().width() / 2; @@ -59,12 +71,6 @@ void TouchscreenVirtualPadDevice::init() { auto& virtualPadManager = VirtualPad::Manager::instance(); setupControlsPositions(virtualPadManager, true); - - if (_fixedPosition) { - virtualPadManager.getLeftVirtualPad()->setShown(virtualPadManager.isEnabled() && !virtualPadManager.isHidden()); // Show whenever it's enabled - } - - KeyboardMouseDevice::enableTouch(false); // Touch for view controls is managed by this plugin } void TouchscreenVirtualPadDevice::setupControlsPositions(VirtualPad::Manager& virtualPadManager, bool force) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h index 212b7633ec..e7e540b503 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h @@ -26,6 +26,7 @@ public: // Plugin functions virtual void init() override; + virtual void resize(); virtual bool isSupported() const override; virtual const QString getName() const override { return NAME; } From dc3914d6eb1f770c8b5bfd8c3ec65f199d4821e5 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 12 Apr 2018 19:23:42 -0300 Subject: [PATCH 19/83] Android - new (empty) activity GotoActivity --- android/app/src/main/AndroidManifest.xml | 7 +++- .../hifiinterface/GotoActivity.java | 38 +++++++++++++++++++ .../hifiinterface/HomeActivity.java | 23 +++++++++-- .../app/src/main/res/layout/activity_goto.xml | 18 +++++++++ android/app/src/main/res/values/strings.xml | 5 ++- 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java create mode 100644 android/app/src/main/res/layout/activity_goto.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c77caa20fb..ed9caee58b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -40,10 +40,15 @@ --> + + + + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index d35b7e5bb2..f380f3cf6e 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ Interface - Home + Home + Go To Open in browser Share link Shared a link @@ -9,6 +10,6 @@ POPULAR BOOKMARKS Settings - Go to + Go To From 20acfdfb82b6ef88ca6a9a502362052c78c72cf0 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 12 Apr 2018 20:31:36 -0300 Subject: [PATCH 20/83] First Android Go To implementation (from the Home screen) --- .../hifiinterface/GotoActivity.java | 45 ++++++++++++++++++- .../app/src/main/res/layout/activity_goto.xml | 16 +++++++ android/app/src/main/res/values/strings.xml | 2 + 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index fbb4953ab3..62636b5ceb 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -1,14 +1,22 @@ package io.highfidelity.hifiinterface; +import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.AppCompatButton; import android.support.v7.widget.Toolbar; +import android.view.KeyEvent; import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; public class GotoActivity extends AppCompatActivity { + private EditText mUrlEditText; + private AppCompatButton mGoBtn; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -20,6 +28,41 @@ public class GotoActivity extends AppCompatActivity { ActionBar actionbar = getSupportActionBar(); actionbar.setDisplayHomeAsUpEnabled(true); + + mUrlEditText = (EditText) findViewById(R.id.url_text); + mUrlEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View view, int i, KeyEvent keyEvent) { + if (i == KeyEvent.KEYCODE_ENTER) { + actionGo(); + return true; + } + return false; + } + }); + mGoBtn = (AppCompatButton) findViewById(R.id.go_btn); + + mGoBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + actionGo(); + } + }); + } + + private void actionGo() { + String urlString = mUrlEditText.getText().toString(); + if (!urlString.trim().isEmpty()) { + Intent intent = new Intent(this, InterfaceActivity.class); + intent.putExtra(InterfaceActivity.DOMAIN_URL, urlString); + finish(); + if (getIntent() != null && + getIntent().hasExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY) && + getIntent().getBooleanExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + } + startActivity(intent); + } } @Override diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml index a10426468e..e4ecda6157 100644 --- a/android/app/src/main/res/layout/activity_goto.xml +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -15,4 +15,20 @@ app:layout_constraintTop_toTopOf="@id/root_activity_goto" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f380f3cf6e..6ce0670dd8 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -11,5 +11,7 @@ BOOKMARKS Settings Go To + Type a domain url + Go From ffb38eb490c7760a5d38440c0607c370926db13b Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 12 Apr 2018 20:45:35 -0300 Subject: [PATCH 21/83] Add hifi scheme for scheme-less addresses --- .../io/highfidelity/hifiinterface/GotoActivity.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index 62636b5ceb..49208a8368 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -12,6 +12,9 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.EditText; +import java.net.URI; +import java.net.URISyntaxException; + public class GotoActivity extends AppCompatActivity { private EditText mUrlEditText; @@ -53,6 +56,16 @@ public class GotoActivity extends AppCompatActivity { private void actionGo() { String urlString = mUrlEditText.getText().toString(); if (!urlString.trim().isEmpty()) { + URI uri; + try { + uri = new URI(urlString); + } catch (URISyntaxException e) { + return; + } + if (uri.getScheme()==null || uri.getScheme().isEmpty()) { + urlString = "hifi://" + urlString; + } + Intent intent = new Intent(this, InterfaceActivity.class); intent.putExtra(InterfaceActivity.DOMAIN_URL, urlString); finish(); From 6b1e22c22698c2c9b76300089c69e0d4572d33c1 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Thu, 12 Apr 2018 20:53:17 -0300 Subject: [PATCH 22/83] Android Go To - Set inputType as textUri --- android/app/src/main/res/layout/activity_goto.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml index e4ecda6157..7f4e7d5fcf 100644 --- a/android/app/src/main/res/layout/activity_goto.xml +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -20,7 +20,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/goto_url_hint" - android:inputType="text" + android:inputType="textUri" android:imeOptions="actionGo" app:layout_constraintTop_toBottomOf="@id/toolbar" /> From a073cba4d4bc357715f3f5a6419ab20487fe5090 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Fri, 13 Apr 2018 12:35:33 -0300 Subject: [PATCH 23/83] Set the current address as default in the Go To screen --- android/app/src/main/cpp/native.cpp | 22 ++++++++++++++++++ .../hifiinterface/GotoActivity.java | 3 +++ .../highfidelity/hifiinterface/HifiUtils.java | 23 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index aac55adf41..c71be76b3e 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -182,4 +182,26 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBack AndroidHelper::instance().goBackFromAndroidActivity(); } +// HifiUtils +JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurrentAddress(JNIEnv *env, jobject instance) { + QSharedPointer addressManager = DependencyManager::get(); + if (!addressManager) { + return env->NewString(nullptr, 0); + } + + QString str; + if (!addressManager->getPlaceName().isEmpty()) { + str = addressManager->getPlaceName(); + } else if (!addressManager->getHost().isEmpty()) { + str = addressManager->getHost(); + } + + QRegExp pathRegEx("(\\/[^\\/]+)"); + if (!addressManager->currentPath().isEmpty() && addressManager->currentPath().contains(pathRegEx) && pathRegEx.matchedLength() > 0) { + str += pathRegEx.cap(0); + } + + return env->NewStringUTF(str.toLatin1().data()); +} + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index 49208a8368..65221bc21c 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -43,6 +43,9 @@ public class GotoActivity extends AppCompatActivity { return false; } }); + + mUrlEditText.setText(HifiUtils.getInstance().getCurrentAddress()); + mGoBtn = (AppCompatButton) findViewById(R.id.go_btn); mGoBtn.setOnClickListener(new View.OnClickListener() { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java new file mode 100644 index 0000000000..15d716548f --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java @@ -0,0 +1,23 @@ +package io.highfidelity.hifiinterface; + +/** + * Created by Gabriel Calero & Cristian Duarte on 4/13/18. + */ + +public class HifiUtils { + + private static HifiUtils instance; + + private HifiUtils() { + } + + public static HifiUtils getInstance() { + if (instance == null) { + instance = new HifiUtils(); + } + return instance; + } + + public native String getCurrentAddress(); + +} From 2e1f2f4214eccf763294f18494a4ed69ef761091 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 13 Apr 2018 14:30:55 -0300 Subject: [PATCH 24/83] Android Go To, minor visual adjustments --- android/app/src/main/res/layout/activity_goto.xml | 7 ++++++- android/app/src/main/res/values/colors.xml | 2 ++ android/app/src/main/res/values/dimens.xml | 2 ++ android/app/src/main/res/values/styles.xml | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml index 7f4e7d5fcf..06e1383da5 100644 --- a/android/app/src/main/res/layout/activity_goto.xml +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -19,6 +19,9 @@ android:id="@+id/url_text" android:layout_width="match_parent" android:layout_height="wrap_content" + style="@style/HifiEditText" + android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" android:hint="@string/goto_url_hint" android:inputType="textUri" android:imeOptions="actionGo" @@ -28,7 +31,9 @@ android:id="@+id/go_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/button_horizontal_margin" android:text="@string/go" - app:layout_constraintTop_toBottomOf="@id/url_text"/> + app:layout_constraintTop_toBottomOf="@id/url_text" + app:layout_constraintEnd_toEndOf="@id/root_activity_goto"/> diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 731aff02d8..0325881f1b 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -8,4 +8,6 @@ #333333 #4F4F4F #33999999 + #212121 + #9e9e9e diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index a9ec657aa9..440adcf6b9 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -9,4 +9,6 @@ 14dp 12dp + 12dp + 8dp \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 2058212651..55c9b2af11 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -48,5 +48,11 @@ @dimen/text_size_subtitle_material_toolbar + From b26bf30904e9ec58469ed8609fdd33b577ff79fc Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Fri, 13 Apr 2018 17:51:29 -0300 Subject: [PATCH 25/83] Android - Make HomeActivity responsible for opening Interface avoiding blank-screen crashes when entering domains more than once --- .../hifiinterface/GotoActivity.java | 13 +++---- .../hifiinterface/HomeActivity.java | 36 +++++++++++++------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index 65221bc21c..a83f93d080 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -17,6 +17,8 @@ import java.net.URISyntaxException; public class GotoActivity extends AppCompatActivity { + public static final String PARAM_DOMAIN_URL = "domain_url"; + private EditText mUrlEditText; private AppCompatButton mGoBtn; @@ -69,15 +71,10 @@ public class GotoActivity extends AppCompatActivity { urlString = "hifi://" + urlString; } - Intent intent = new Intent(this, InterfaceActivity.class); - intent.putExtra(InterfaceActivity.DOMAIN_URL, urlString); + Intent intent = new Intent(); + intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString); + setResult(RESULT_OK, intent); finish(); - if (getIntent() != null && - getIntent().hasExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY) && - getIntent().getBooleanExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - startActivity(intent); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index ed442e2d8d..fd50d36c75 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -31,6 +31,9 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On * Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected" */ public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity"; + + public static final int ENTER_DOMAIN_URL = 1; + private DomainAdapter domainAdapter; private DrawerLayout mDrawerLayout; private ProgressDialog mDialog; @@ -89,15 +92,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On @Override public void onItemClick(View view, int position, DomainAdapter.Domain domain) { - Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); - intent.putExtra(InterfaceActivity.DOMAIN_URL, domain.url); - HomeActivity.this.finish(); - if (getIntent() != null && - getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) && - getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - startActivity(intent); + gotoDomain(domain.url); } }); domainsView.setAdapter(domainAdapter); @@ -121,6 +116,18 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On } + private void gotoDomain(String domainUrl) { + Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); + intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl); + HomeActivity.this.finish(); + if (getIntent() != null && + getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) && + getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + } + startActivity(intent); + } + private void showActivityIndicator() { if (mDialog == null) { mDialog = new ProgressDialog(this); @@ -190,11 +197,20 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On switch(item.getItemId()) { case R.id.action_goto: Intent i = new Intent(this, GotoActivity.class); - startActivity(i); + startActivityForResult(i, ENTER_DOMAIN_URL); return true; case R.id.action_settings: return true; } return false; } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == ENTER_DOMAIN_URL && resultCode == RESULT_OK) { + gotoDomain(data.getStringExtra(GotoActivity.PARAM_DOMAIN_URL)); + } + } + } From 472cc1b29a2625452611372f80de9fb0451d7c55 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 18 Apr 2018 16:44:49 -0300 Subject: [PATCH 26/83] Fix code spacing --- android/app/src/main/assets/hifi_domains.json | 2 +- .../io/highfidelity/hifiinterface/GotoActivity.java | 2 +- .../io/highfidelity/hifiinterface/HomeActivity.java | 10 +++++----- .../highfidelity/hifiinterface/InterfaceActivity.java | 2 +- .../io/highfidelity/interface/InterfaceActivity.java | 1 - scripts/system/+android/actionbar.js | 2 +- scripts/system/+android/bottombar.js | 4 ++-- scripts/system/+android/modes.js | 2 +- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/android/app/src/main/assets/hifi_domains.json b/android/app/src/main/assets/hifi_domains.json index 216b24e639..a63ef7b6aa 100644 --- a/android/app/src/main/assets/hifi_domains.json +++ b/android/app/src/main/assets/hifi_domains.json @@ -1,7 +1,7 @@ { "hifi_domains" : [ { - "name": "You last location", + "name": "Your last location", "url": "", "thumbnail": "" }, diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index a83f93d080..995e64c2a5 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -67,7 +67,7 @@ public class GotoActivity extends AppCompatActivity { } catch (URISyntaxException e) { return; } - if (uri.getScheme()==null || uri.getScheme().isEmpty()) { + if (uri.getScheme() == null || uri.getScheme().isEmpty()) { urlString = "hifi://" + urlString; } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index fd50d36c75..63e0870d58 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -56,7 +56,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On mNavigationView = (NavigationView) findViewById(R.id.nav_view); mNavigationView.setNavigationItemSelectedListener(this); - TabHost tabs=(TabHost)findViewById(R.id.tabhost); + TabHost tabs = (TabHost)findViewById(R.id.tabhost); tabs.setup(); TabHost.TabSpec spec=tabs.newTabSpec("featured"); @@ -64,12 +64,12 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On spec.setIndicator(getString(R.string.featured)); tabs.addTab(spec); - spec=tabs.newTabSpec("popular"); + spec = tabs.newTabSpec("popular"); spec.setContent(R.id.popular); spec.setIndicator(getString(R.string.popular)); tabs.addTab(spec); - spec=tabs.newTabSpec("bookmarks"); + spec = tabs.newTabSpec("bookmarks"); spec.setContent(R.id.bookmarks); spec.setIndicator(getString(R.string.bookmarks)); tabs.addTab(spec); @@ -77,7 +77,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On tabs.setCurrentTab(0); TabWidget tabwidget=tabs.getTabWidget(); - for(int i=0;i Date: Thu, 19 Apr 2018 11:41:59 -0300 Subject: [PATCH 27/83] Remove debug statements and unused code. Put JNI code in a separated file. --- .../hifiinterface/InterfaceActivity.java | 2 +- .../resources/qml/hifi/+android/modesbar.qml | 2 +- interface/src/Application.cpp | 48 ++----------------- interface/src/Application.h | 2 - interface/src/Application_jni.cpp | 18 +++++++ scripts/system/+android/actionbar.js | 2 +- scripts/system/+android/goto.js | 2 +- 7 files changed, 25 insertions(+), 51 deletions(-) create mode 100644 interface/src/Application_jni.cpp 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 52da11f1a9..32b6e0e039 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -199,7 +199,7 @@ public class InterfaceActivity extends QtActivity { public void openGotoActivity(String activityName) { switch (activityName) { - case "Goto": { + case "Home": { Intent intent = new Intent(this, HomeActivity.class); intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true); startActivity(intent); diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 98973f1edb..994bf1efe4 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -15,7 +15,7 @@ Item { function relocateAndResize(newWindowWidth, newWindowHeight) { width = 300; height = 300; - x=newWindowWidth - 565; + x = newWindowWidth - 565; } function onWindowGeometryChanged(rect) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9fee772513..19267a59ee 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7862,71 +7862,29 @@ void Application::saveNextPhysicsStats(QString filename) { void Application::openAndroidActivity(const QString& activityName) { #if defined(Q_OS_ANDROID) - qDebug() << "[Background-HIFI] Application::openAndroidActivity"; - //getActiveDisplayPlugin()->deactivate(); AndroidHelper::instance().requestActivity(activityName); - connect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity); -#endif -} - -void Application::restoreAfterAndroidActivity() { -#if defined(Q_OS_ANDROID) - qDebug() << "[Background-HIFI] restoreAfterAndroidActivity: this wouldn't be needed"; - - /*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { - qWarning() << "Could not re-activate display plugin"; - }*/ - disconnect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity); #endif } #if defined(Q_OS_ANDROID) void Application::enterBackground() { - qDebug() << "[Background-HIFI] enterBackground begin"; QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); - qDebug() << "[Background-HIFI] deactivating display plugin"; getActiveDisplayPlugin()->deactivate(); - qDebug() << "[Background-HIFI] enterBackground end"; } void Application::enterForeground() { - qDebug() << "[Background-HIFI] enterForeground qApp?" << (qApp?"yeah":"false"); if (qApp && DependencyManager::isSet()) { - qDebug() << "[Background-HIFI] audioclient.start()"; QMetaObject::invokeMethod(DependencyManager::get().data(), "start", Qt::BlockingQueuedConnection); } else { - qDebug() << "[Background-HIFI] audioclient.start() not done"; + qDebug() << "Could not start AudioClient"; } if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { - qWarning() << "[Background-HIFI] Could not re-activate display plugin"; + qWarning() << "Could not re-activate display plugin"; } } - -extern "C" { - - -JNIEXPORT void -Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) { - qDebug() << "[Background-HIFI] nativeEnterBackground"; - if (qApp) { - qDebug() << "[Background-HIFI] nativeEnterBackground begin (qApp)"; - qApp->enterBackground(); - } -} - -JNIEXPORT void -Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) { - qDebug() << "[Background-HIFI] nativeEnterForeground"; - if (qApp) { - qDebug() << "[Background-HIFI] nativeEnterForeground begin (qApp)"; - qApp->enterForeground(); - } -} - - -} +#include "Application_jni.cpp" #endif diff --git a/interface/src/Application.h b/interface/src/Application.h index ec49aa055f..568cce2ab6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -459,8 +459,6 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); - void restoreAfterAndroidActivity(); - private: static void initDisplay(); void init(); diff --git a/interface/src/Application_jni.cpp b/interface/src/Application_jni.cpp new file mode 100644 index 0000000000..83641ad8c6 --- /dev/null +++ b/interface/src/Application_jni.cpp @@ -0,0 +1,18 @@ +extern "C" { + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) { + if (qApp) { + qApp->enterBackground(); + } +} + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) { + if (qApp) { + qApp->enterForeground(); + } +} + + +} diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android/actionbar.js index a1ddc6f2de..1f0872d5ee 100644 --- a/scripts/system/+android/actionbar.js +++ b/scripts/system/+android/actionbar.js @@ -38,7 +38,7 @@ function init() { } function onBackPressed() { - App.openAndroidActivity("Goto"); + App.openAndroidActivity("Home"); } diff --git a/scripts/system/+android/goto.js b/scripts/system/+android/goto.js index 540705c673..750844a2a4 100644 --- a/scripts/system/+android/goto.js +++ b/scripts/system/+android/goto.js @@ -34,7 +34,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See module.exports.onHidden(); break; case 'openAndroidActivity': - App.openAndroidActivity("Goto"); + App.openAndroidActivity("Home"); break; default: print('[goto-android.js] Unrecognized message from AddressBarDialog.qml:', JSON.stringify(message)); From a0b5d9a78d5fb9c9af3c83fa4453f2c00cd122c2 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 19 Apr 2018 14:33:38 -0300 Subject: [PATCH 28/83] Rename openGotoActivity to openAndroidActivity --- android/app/src/main/cpp/native.cpp | 2 +- .../java/io/highfidelity/hifiinterface/InterfaceActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index c71be76b3e..facf6bd4bd 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -151,7 +151,7 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) { QAndroidJniObject string = QAndroidJniObject::fromString(a); - __activity.callMethod("openGotoActivity", "(Ljava/lang/String;)V", string.object()); + __activity.callMethod("openAndroidActivity", "(Ljava/lang/String;)V", string.object()); }); } 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 32b6e0e039..dd758704e0 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -197,7 +197,7 @@ public class InterfaceActivity extends QtActivity { nativeGoBackFromAndroidActivity(); } - public void openGotoActivity(String activityName) { + public void openAndroidActivity(String activityName) { switch (activityName) { case "Home": { Intent intent = new Intent(this, HomeActivity.class); From f2184bf4568f7c388a1883954a58e38e1ca15792 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Thu, 19 Apr 2018 17:31:25 -0300 Subject: [PATCH 29/83] Handle android back button. Remove unused resources. Fix compilation errors. --- .../hifiinterface/HomeActivity.java | 4 + .../hifiinterface/InterfaceActivity.java | 4 + .../resources/icons/+android/avatar-a.svg | 38 - .../resources/icons/+android/avatar-i.svg | 38 - interface/resources/icons/+android/goto-a.svg | 28 - interface/resources/icons/+android/goto-i.svg | 28 - interface/resources/icons/+android/home.svg | 82 -- .../resources/icons/+android/login-a.svg | 990 ------------------ .../resources/icons/+android/login-i.svg | 990 ------------------ .../qml/+android/AddressBarDialog.qml | 232 ---- .../resources/qml/+android/LoginDialog.qml | 95 -- .../qml/hifi/+android/avatarSelection.qml | 179 ---- .../resources/qml/hifi/+android/bottombar.qml | 151 --- interface/src/Application.cpp | 17 +- interface/src/Application.h | 4 - interface/src/Application_jni.cpp | 6 + scripts/system/+android/avatarSelection.js | 164 --- scripts/system/+android/bottombar.js | 271 ----- scripts/system/+android/goto.js | 108 -- 19 files changed, 23 insertions(+), 3406 deletions(-) delete mode 100755 interface/resources/icons/+android/avatar-a.svg delete mode 100755 interface/resources/icons/+android/avatar-i.svg delete mode 100755 interface/resources/icons/+android/goto-a.svg delete mode 100644 interface/resources/icons/+android/goto-i.svg delete mode 100644 interface/resources/icons/+android/home.svg delete mode 100755 interface/resources/icons/+android/login-a.svg delete mode 100755 interface/resources/icons/+android/login-i.svg delete mode 100644 interface/resources/qml/+android/AddressBarDialog.qml delete mode 100644 interface/resources/qml/+android/LoginDialog.qml delete mode 100644 interface/resources/qml/hifi/+android/avatarSelection.qml delete mode 100644 interface/resources/qml/hifi/+android/bottombar.qml delete mode 100644 scripts/system/+android/avatarSelection.js delete mode 100644 scripts/system/+android/bottombar.js delete mode 100644 scripts/system/+android/goto.js diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index 63e0870d58..611c8f50cc 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -213,4 +213,8 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On } } + @Override + public void onBackPressed() { + finishAffinity(); + } } 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 dd758704e0..d2aff85323 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -212,4 +212,8 @@ public class InterfaceActivity extends QtActivity { } } + @Override + public void onBackPressed() { + openAndroidActivity("Home"); + } } \ No newline at end of file diff --git a/interface/resources/icons/+android/avatar-a.svg b/interface/resources/icons/+android/avatar-a.svg deleted file mode 100755 index 165b39943e..0000000000 --- a/interface/resources/icons/+android/avatar-a.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - diff --git a/interface/resources/icons/+android/avatar-i.svg b/interface/resources/icons/+android/avatar-i.svg deleted file mode 100755 index c1557487ea..0000000000 --- a/interface/resources/icons/+android/avatar-i.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - diff --git a/interface/resources/icons/+android/goto-a.svg b/interface/resources/icons/+android/goto-a.svg deleted file mode 100755 index 5fb3e52e4c..0000000000 --- a/interface/resources/icons/+android/goto-a.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/goto-i.svg b/interface/resources/icons/+android/goto-i.svg deleted file mode 100644 index 7613beb9e7..0000000000 --- a/interface/resources/icons/+android/goto-i.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/home.svg b/interface/resources/icons/+android/home.svg deleted file mode 100644 index 414c179e79..0000000000 --- a/interface/resources/icons/+android/home.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/+android/login-a.svg b/interface/resources/icons/+android/login-a.svg deleted file mode 100755 index 8a7f097ed7..0000000000 --- a/interface/resources/icons/+android/login-a.svg +++ /dev/null @@ -1,990 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/interface/resources/icons/+android/login-i.svg b/interface/resources/icons/+android/login-i.svg deleted file mode 100755 index 6f011e1d13..0000000000 --- a/interface/resources/icons/+android/login-i.svg +++ /dev/null @@ -1,990 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/interface/resources/qml/+android/AddressBarDialog.qml b/interface/resources/qml/+android/AddressBarDialog.qml deleted file mode 100644 index e3fcad2b5e..0000000000 --- a/interface/resources/qml/+android/AddressBarDialog.qml +++ /dev/null @@ -1,232 +0,0 @@ -// -// AddressBarDialog.qml -// -// Created by Austin Davis on 2015/04/14 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import Hifi 1.0 -import QtQuick 2.4 -import "../controls" -import "../styles" -import "../hifi" as QmlHifi -import "../hifi/toolbars" -import "../styles-uit" as HifiStyles -import "../controls-uit" as HifiControls - -Item { - QmlHifi.HifiConstants { id: android } - - width: parent ? parent.width - android.dimen.windowLessWidth : 0 - height: parent ? parent.height - android.dimen.windowLessHeight : 0 - z: android.dimen.windowZ - anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } - - id: bar - property bool isCursorVisible: false // Override default cursor visibility. - property bool shown: true - - onShownChanged: { - bar.visible = shown; - sendToScript({method: 'shownChanged', params: { shown: shown }}); - if (shown) { - addressLine.text=""; - updateLocationText(addressLine.text.length > 0); - } - } - - function hide() { - shown = false; - sendToScript ({ type: "hide" }); - } - - Component.onCompleted: { - updateLocationText(addressLine.text.length > 0); - } - - HifiConstants { id: hifi } - HifiStyles.HifiConstants { id: hifiStyleConstants } - - signal sendToScript(var message); - - AddressBarDialog { - id: addressBarDialog - } - - - Rectangle { - id: background - gradient: Gradient { - GradientStop { position: 0.0; color: android.color.gradientTop } - GradientStop { position: 1.0; color: android.color.gradientBottom } - } - anchors { - fill: parent - } - - MouseArea { - anchors.fill: parent - } - - QmlHifi.WindowHeader { - id: header - iconSource: "../../../icons/goto-i.svg" - titleText: "GO TO" - } - - HifiStyles.RalewayRegular { - id: notice - text: "YOUR LOCATION" - font.pixelSize: (hifi.fonts.pixelSize * 2.15) * (android.dimen.atLeast1440p ? 1 : .75); - color: "#2CD7FF" - anchors { - bottom: addressBackground.top - bottomMargin: android.dimen.atLeast1440p ? 45 : 34 - left: addressBackground.left - leftMargin: android.dimen.atLeast1440p ? 60 : 45 - } - - } - - property int inputAreaHeight: android.dimen.atLeast1440p ? 210 : 156 - property int inputAreaStep: (height - inputAreaHeight) / 2 - - ToolbarButton { - id: homeButton - y: android.dimen.atLeast1440p ? 280 : 210 - imageURL: "../../icons/home.svg" - onClicked: { - sendToScript({method: 'openAndroidActivity', params: {}}); - hide(); - } - anchors { - leftMargin: android.dimen.atLeast1440p ? 75 : 56 - left: parent.left - } - size: android.dimen.atLeast1440p ? 150 : 150//112 - } - - ToolbarButton { - id: backArrow; - imageURL: "../../icons/backward.svg"; - onClicked: addressBarDialog.loadBack(); - anchors { - left: homeButton.right - leftMargin: android.dimen.atLeast1440p ? 70 : 52 - verticalCenter: homeButton.verticalCenter - } - size: android.dimen.atLeast1440p ? 150 : 150 - } - ToolbarButton { - id: forwardArrow; - imageURL: "../../icons/forward.svg"; - onClicked: addressBarDialog.loadForward(); - anchors { - left: backArrow.right - leftMargin: android.dimen.atLeast1440p ? 60 : 45 - verticalCenter: homeButton.verticalCenter - } - size: android.dimen.atLeast1440p ? 150 : 150 - } - - HifiStyles.FiraSansRegular { - id: location; - font.pixelSize: addressLine.font.pixelSize; - color: "lightgray"; - clip: true; - anchors.fill: addressLine; - visible: addressLine.text.length === 0 - z: 1 - } - - Rectangle { - id: addressBackground - x: android.dimen.atLeast1440p ? 780 : 585 - y: android.dimen.atLeast1440p ? 280 : 235 // tweaking by hand - width: android.dimen.atLeast1440p ? 1270 : 952 - height: android.dimen.atLeast1440p ? 150 : 112 - color: "#FFFFFF" - } - - TextInput { - id: addressLine - focus: true - x: android.dimen.atLeast1440p ? 870 : 652 - y: android.dimen.atLeast1440p ? 300 : 245 // tweaking by hand - width: android.dimen.atLeast1440p ? 1200 : 900 - height: android.dimen.atLeast1440p ? 120 : 90 - inputMethodHints: Qt.ImhNoPredictiveText - //helperText: "Hint is here" - font.pixelSize: hifi.fonts.pixelSize * 3.75 - onTextChanged: { - //filterChoicesByText(); - updateLocationText(addressLine.text.length > 0); - if (!isCursorVisible && text.length > 0) { - isCursorVisible = true; - cursorVisible = true; - } - } - - onActiveFocusChanged: { - //cursorVisible = isCursorVisible && focus; - } - } - - - - function toggleOrGo() { - if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text); - } - bar.shown = false; - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - clearAddressLineTimer.start(); - event.accepted = true - bar.shown = false; - break - case Qt.Key_Enter: - case Qt.Key_Return: - toggleOrGo(); - clearAddressLineTimer.start(); - event.accepted = true - break - } - } - - } - - Timer { - // Delay clearing address line so as to avoid flicker of "not connected" being displayed after entering an address. - id: clearAddressLineTimer - running: false - interval: 100 // ms - repeat: false - onTriggered: { - addressLine.text = ""; - isCursorVisible = false; - } - } - - function updateLocationText(enteringAddress) { - if (enteringAddress) { - notice.text = "Go to a place, @user, path or network address"; - notice.color = "#ffffff"; // hifiStyleConstants.colors.baseGrayHighlight; - location.visible = false; - } else { - notice.text = AddressManager.isConnected ? "YOUR LOCATION:" : "NOT CONNECTED"; - notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.blueHighlight : hifiStyleConstants.colors.redHighlight; - // Display hostname, which includes ip address, localhost, and other non-placenames. - location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); - location.visible = true; - } - } - -} diff --git a/interface/resources/qml/+android/LoginDialog.qml b/interface/resources/qml/+android/LoginDialog.qml deleted file mode 100644 index 567cca9bcf..0000000000 --- a/interface/resources/qml/+android/LoginDialog.qml +++ /dev/null @@ -1,95 +0,0 @@ -// -// LoginDialog.qml -// -// Created by David Rowe on 3 Jun 2015 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import Hifi 1.0 -import QtQuick 2.4 - -import "controls-uit" -import "styles-uit" -import "windows" - -import "LoginDialog" - -ModalWindow { - id: root - HifiConstants { id: hifi } - - objectName: "LoginDialog" - implicitWidth: 1560 - implicitHeight: 450 - y:0 - destroyOnCloseButton: true - destroyOnHidden: true - visible: true - - property string iconText: "" - property int iconSize: 105 - - property string title: "" - property int titleWidth: 0 - - keyboardOverride: true // Disable ModalWindow's keyboard. - - function tryDestroy() { - Controller.setVPadHidden(false); - root.destroy(); - } - - LoginDialog { - id: loginDialog - - Loader { - id: bodyLoader - source: loginDialog.isSteamRunning() ? "LoginDialog/+android/SignInBody.qml" : "LoginDialog/+android/LinkAccountBody.qml" - } - } - - Component.onCompleted: { - this.anchors.centerIn = undefined; - this.y = 150; - this.x = (parent.width - this.width) / 2; - Controller.setVPadHidden(true); - } - - Keys.onPressed: { - if (!visible) { - return - } - - if (event.modifiers === Qt.ControlModifier) - switch (event.key) { - case Qt.Key_A: - event.accepted = true - detailedText.selectAll() - break - case Qt.Key_C: - event.accepted = true - detailedText.copy() - break - case Qt.Key_Period: - if (Qt.platform.os === "osx") { - event.accepted = true - content.reject() - } - break - } else switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - event.accepted = true - destroy() - break - - case Qt.Key_Enter: - case Qt.Key_Return: - event.accepted = true - break - } - } -} diff --git a/interface/resources/qml/hifi/+android/avatarSelection.qml b/interface/resources/qml/hifi/+android/avatarSelection.qml deleted file mode 100644 index afa5634575..0000000000 --- a/interface/resources/qml/hifi/+android/avatarSelection.qml +++ /dev/null @@ -1,179 +0,0 @@ -// -// avatarSelection.qml -// interface/resources/qml/android -// -// Created by Gabriel Calero & Cristian Duarte on 21 Sep 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -import QtQuick 2.5 -import QtQuick.Layouts 1.3 -import Hifi 1.0 - -import "../../styles" -import "." -import ".." -import ".." as QmlHifi -import "../../styles-uit" as HifiStyles - - -Item { - - id: top - - HifiConstants { id: android } - width: parent ? parent.width - android.dimen.windowLessWidth : 0 - height: parent ? parent.height - android.dimen.windowLessHeight : 0 - z: android.dimen.windowZ - anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } - - signal sendToScript(var message); - - property bool shown: true - - onShownChanged: { - top.visible = shown; - } - - - HifiConstants { id: hifi } - HifiStyles.HifiConstants { id: hifiStyleConstants } - - property int cardWidth: 250 *3; - property int cardHeight: 240 *3; - property int gap: 14 *3; - - property var avatarsArray: []; - property var extraOptionsArray: []; - - function hide() { - shown = false; - sendToScript ({ method: "hide" }); - } - - Rectangle { - - width: parent ? parent.width : 0 - height: parent ? parent.height : 0 - - MouseArea { - anchors.fill: parent - } - - gradient: Gradient { - GradientStop { position: 0.0; color: android.color.gradientTop } - GradientStop { position: 1.0; color: android.color.gradientBottom } - } - - QmlHifi.WindowHeader { - id: header - iconSource: "../../../../icons/avatar-i.svg" - titleText: "AVATAR" - } - - ListModel { id: avatars } - - ListView { - id: scroll - height: 250*3 - property int stackedCardShadowHeight: 10*3; - spacing: gap; - clip: true; - anchors { - left: parent.left - right: parent.right - top: header.bottom - topMargin: gap - leftMargin: gap - rightMargin: gap - } - model: avatars; - orientation: ListView.Horizontal; - delegate: QmlHifi.AvatarOption { - type: model.type; - thumbnailUrl: model.thumbnailUrl; - avatarUrl: model.avatarUrl; - avatarName: model.avatarName; - avatarSelected: model.avatarSelected; - methodName: model.methodName; - actionText: model.actionText; - } - highlightMoveDuration: -1; - highlightMoveVelocity: -1; - } - - } - - function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); - } - function replaceAll(str, find, replace) { - return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); - } - - function refreshSelected(selectedAvatarUrl) { - // URL as ID? - avatarsArray.forEach(function (avatarData) { - avatarData.avatarSelected = (selectedAvatarUrl == avatarData.avatarUrl); - console.log('[avatarSelection] avatar : ', avatarData.avatarName, ' is selected? ' , avatarData.avatarSelected); - }); - } - - function addAvatar(name, thumbnailUrl, avatarUrl) { - avatarsArray.push({ - type: "avatar", - thumbnailUrl: thumbnailUrl, - avatarUrl: avatarUrl, - avatarName: name, - avatarSelected: false, - methodName: "", - actionText: "" - }); - } - - function showAvatars() { - avatars.clear(); - avatarsArray.forEach(function (avatarData) { - avatars.append(avatarData); - console.log('[avatarSelection] adding avatar to model: ', JSON.stringify(avatarData)); - }); - extraOptionsArray.forEach(function (extraData) { - avatars.append(extraData); - console.log('[avatarSelection] adding extra option to model: ', JSON.stringify(extraData)); - }); - } - - function addExtraOption(showName, thumbnailUrl, methodNameWhenClicked, actionText) { - extraOptionsArray.push({ - type: "extra", - thumbnailUrl: thumbnailUrl, - avatarUrl: "", - avatarName: showName, - avatarSelected: false, - methodName: methodNameWhenClicked, - actionText: actionText - }); - } - - function fromScript(message) { - //console.log("[CHAT] fromScript " + JSON.stringify(message)); - switch (message.type) { - case "addAvatar": - addAvatar(message.name, message.thumbnailUrl, message.avatarUrl); - break; - case "addExtraOption": - //(showName, thumbnailUrl, methodNameWhenClicked, actionText) - addExtraOption(message.showName, message.thumbnailUrl, message.methodNameWhenClicked, message.actionText); - break; - case "refreshSelected": - refreshSelected(message.selectedAvatarUrl); - break; - case "showAvatars": - showAvatars(); - break; - default: - } - } -} \ No newline at end of file diff --git a/interface/resources/qml/hifi/+android/bottombar.qml b/interface/resources/qml/hifi/+android/bottombar.qml deleted file mode 100644 index 66117d0389..0000000000 --- a/interface/resources/qml/hifi/+android/bottombar.qml +++ /dev/null @@ -1,151 +0,0 @@ -// -// bottomHudOptions.qml -// interface/resources/qml/android -// -// Created by Gabriel Calero & Cristian Duarte on 19 Jan 2018 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import Hifi 1.0 -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Layouts 1.3 -import Qt.labs.settings 1.0 -import "../../styles" as Styles -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls -import ".." -import "." - -Item { - id: bar - x:0 - height: 255 - - property bool shown: true - - signal sendToScript(var message); - - onShownChanged: { - bar.visible = shown; - } - - function hide() { - //shown = false; - sendToScript({ method: "hide" }); - } - - Styles.HifiConstants { id: hifi } - HifiConstants { id: android } - MouseArea { - anchors.fill: parent - } - - Rectangle { - id: background - anchors.fill : parent - color: "#FF000000" - border.color: "#FFFFFF" - anchors.bottomMargin: -1 - anchors.leftMargin: -1 - anchors.rightMargin: -1 - Flow { - id: flowMain - spacing: 10 - anchors.fill: parent - anchors.topMargin: 12 - anchors.bottomMargin: 12 - anchors.rightMargin: 12 - anchors.leftMargin: 72 - } - - - Rectangle { - id: hideButton - height: android.dimen.headerHideWidth - width: android.dimen.headerHideHeight - color: "#00000000" - anchors { - right: parent.right - rightMargin: android.dimen.headerHideRightMargin - top: parent.top - topMargin: android.dimen.headerHideTopMargin - } - - Image { - id: hideIcon - source: "../../../icons/hide.svg" - width: android.dimen.headerHideIconWidth - height: android.dimen.headerHideIconHeight - anchors { - horizontalCenter: parent.horizontalCenter - top: parent.top - } - } - FiraSansRegular { - anchors { - top: hideIcon.bottom - horizontalCenter: hideIcon.horizontalCenter - topMargin: 12 - } - text: "HIDE" - color: "#FFFFFF" - font.pixelSize: hifi.fonts.pixelSize * 2.5; - } - - MouseArea { - anchors.fill: parent - onClicked: { - hide(); - } - } - } - } - - function relocateAndResize(newWindowWidth, newWindowHeight) { - width = newWindowWidth; - y = newWindowHeight - height; - } - - function onWindowGeometryChanged(rect) { - relocateAndResize(rect.width, rect.height); - } - - Component.onCompleted: { - // put on bottom - relocateAndResize(Window.innerWidth, Window.innerHeight); - Window.geometryChanged.connect(onWindowGeometryChanged); // In devices with bars appearing at startup we should listen for this - } - - Component.onDestruction: { - Window.geometryChanged.disconnect(onWindowGeometryChanged); - } - - function addButton(properties) { - var component = Qt.createComponent("button.qml"); - if (component.status == Component.Ready) { - var button = component.createObject(flowMain); - // copy all properites to button - var keys = Object.keys(properties).forEach(function (key) { - button[key] = properties[key]; - }); - return button; - } else if( component.status == Component.Error) { - console.log("Load button errors " + component.errorString()); - } - } - - function urlHelper(src) { - if (src.match(/\bhttp/)) { - return src; - } else { - return "../../../" + src; - } - } - -} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 19267a59ee..b478dd1807 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -242,7 +242,7 @@ extern "C" { #if defined(Q_OS_ANDROID) #include -#include +#include "AndroidHelper.h" #endif enum ApplicationEvent { @@ -3623,6 +3623,12 @@ void Application::keyPressEvent(QKeyEvent* event) { void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); +#if defined(Q_OS_ANDROID) + if (event->key() == Qt::Key_Back) { + event->accept(); + openAndroidActivity("Home"); + } +#endif _controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -7873,19 +7879,14 @@ void Application::enterBackground() { getActiveDisplayPlugin()->deactivate(); } void Application::enterForeground() { - if (qApp && DependencyManager::isSet()) { - QMetaObject::invokeMethod(DependencyManager::get().data(), + QMetaObject::invokeMethod(DependencyManager::get().data(), "start", Qt::BlockingQueuedConnection); - } else { - qDebug() << "Could not start AudioClient"; - } if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { qWarning() << "Could not re-activate display plugin"; } } -#include "Application_jni.cpp" - #endif +#include "Application_jni.cpp" #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index 568cce2ab6..7af8a679bf 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -78,10 +78,6 @@ #include "Sound.h" -#if defined(Q_OS_ANDROID) -#include "AndroidHelper.h" -#endif - class OffscreenGLCanvas; class GLCanvas; class FaceTracker; diff --git a/interface/src/Application_jni.cpp b/interface/src/Application_jni.cpp index 83641ad8c6..5e9f1ac29e 100644 --- a/interface/src/Application_jni.cpp +++ b/interface/src/Application_jni.cpp @@ -1,3 +1,8 @@ +#if defined(Q_OS_ANDROID) + +#include +#include "AndroidHelper.h" + extern "C" { JNIEXPORT void @@ -16,3 +21,4 @@ Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEn } +#endif \ No newline at end of file diff --git a/scripts/system/+android/avatarSelection.js b/scripts/system/+android/avatarSelection.js deleted file mode 100644 index 2946e541b5..0000000000 --- a/scripts/system/+android/avatarSelection.js +++ /dev/null @@ -1,164 +0,0 @@ -"use strict"; -// -// avatarSelection.js -// scripts/system/ -// -// Created by Gabriel Calero & Cristian Duarte on 21 Sep 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var window; - -var logEnabled = true; -var isVisible = false; - -function printd(str) { - if (logEnabled) - print("[avatarSelection.js] " + str); -} - -function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - var data; - printd("fromQml " + JSON.stringify(message)); - switch (message.method) { - case 'selectAvatar': - // use this message.params.avatarUrl - printd("Selected Avatar: [" + message.params.avatarUrl + "]"); - App.askBeforeSetAvatarUrl(message.params.avatarUrl); - break; - case 'openAvatarMarket': - // good - App.openUrl("https://metaverse.highfidelity.com/marketplace?category=avatars"); - break; - case 'hide': - Controller.setVPadHidden(false); - module.exports.hide(); - module.exports.onHidden(); - break; - default: - print('[avatarSelection.js] Unrecognized message from avatarSelection.qml:', JSON.stringify(message)); - } -} - -function sendToQml(message) { - if (!window) { - print("[avatarSelection.js] There is no window object"); - return; - } - window.sendToQml(message); -} - -function refreshSelected(currentAvatarURL) { - sendToQml({ - type: "refreshSelected", - selectedAvatarUrl: currentAvatarURL - }); - - sendToQml({ - type: "showAvatars" - }); -} - -function init() { - if (!window) { - print("[avatarSelection.js] There is no window object for init()"); - return; - } - var DEFAULT_AVATAR_URL = "http://mpassets.highfidelity.com/7fe80a1e-f445-4800-9e89-40e677b03bee-v3/mannequin.fst"; - sendToQml({ - type: "addAvatar", - name: "Wooden Mannequin", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/thumbnail/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg", - avatarUrl: DEFAULT_AVATAR_URL - }); - sendToQml({ - type: "addAvatar", - name: "Cody", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53/thumbnail/hifi-mp-8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53.jpg", - avatarUrl: "http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst" - }); - sendToQml({ - type: "addAvatar", - name: "Mixamo Will", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73/thumbnail/hifi-mp-d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73.jpg", - avatarUrl: "http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst" - }); - sendToQml({ - type: "addAvatar", - name: "Albert", - thumbnailUrl: "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e57c395-612e-4acd-9561-e79dbda0bc49/thumbnail/hifi-mp-1e57c395-612e-4acd-9561-e79dbda0bc49.jpg", - avatarUrl: "http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" - }); - /* We need to implement the wallet, so let's skip this for the moment - sendToQml({ - type: "addExtraOption", - showName: "More choices", - thumbnailUrl: "../../../images/moreAvatars.png", - methodNameWhenClicked: "openAvatarMarket", - actionText: "MARKETPLACE" - }); - */ - var currentAvatarURL = Settings.getValue('Avatar/fullAvatarURL', DEFAULT_AVATAR_URL); - printd("Default Avatar: [" + DEFAULT_AVATAR_URL + "]"); - printd("Current Avatar: [" + currentAvatarURL + "]"); - if (!currentAvatarURL || 0 === currentAvatarURL.length) { - currentAvatarURL = DEFAULT_AVATAR_URL; - } - refreshSelected(currentAvatarURL); -} - -module.exports = { - init: function() { - window = new QmlFragment({ - qml: "hifi/avatarSelection.qml", - visible: false - }); - if (window) { - window.fromQml.connect(fromQml); - } - init(); - }, - show: function() { - Controller.setVPadHidden(true); - if (window) { - window.setVisible(true); - isVisible = true; - } - }, - hide: function() { - Controller.setVPadHidden(false); - if (window) { - window.setVisible(false); - } - isVisible = false; - }, - destroy: function() { - Controller.setVPadHidden(false); - if (window) { - window.fromQml.disconnect(fromQml); - window.close(); - window = null; - } - }, - isVisible: function() { - return isVisible; - }, - width: function() { - return window ? window.size.x : 0; - }, - height: function() { - return window ? window.size.y : 0; - }, - position: function() { - return window && isVisible ? window.position : null; - }, - refreshSelectedAvatar: function(currentAvatarURL) { - refreshSelected(currentAvatarURL); - }, - onHidden: function() { - Controller.setVPadHidden(false); - } -}; diff --git a/scripts/system/+android/bottombar.js b/scripts/system/+android/bottombar.js deleted file mode 100644 index 526cd128e5..0000000000 --- a/scripts/system/+android/bottombar.js +++ /dev/null @@ -1,271 +0,0 @@ -"use strict"; -// -// bottombar.js -// scripts/system/ -// -// Created by Gabriel Calero & Cristian Duarte on Jan 18, 2018 -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -(function() { // BEGIN LOCAL_SCOPE - -var bottombar; -var bottomHudOptionsBar; -var gotoBtn; -var avatarBtn; -var bubbleBtn; -var loginBtn; - -var gotoScript = Script.require('./goto.js'); -var avatarSelection = Script.require('./avatarSelection.js'); - -var logEnabled = false; - -function printd(str) { - if (logEnabled) { - print("[bottombar.js] " + str); - } -} - -function init() { - gotoScript.init(); - gotoScript.setOnShownChange(function (shown) { - if (shown) { - showAddressBar(); - } else { - hideAddressBar(); - } - }); - avatarSelection.init(); - App.fullAvatarURLChanged.connect(processedNewAvatar); - - setupBottomBar(); - setupBottomHudOptionsBar(); - - raiseBottomBar(); - - GlobalServices.connected.connect(handleLogin); - GlobalServices.myUsernameChanged.connect(onUsernameChanged); - GlobalServices.disconnected.connect(handleLogout); -} - -function shutdown() { - App.fullAvatarURLChanged.disconnect(processedNewAvatar); -} - -function setupBottomBar() { - bottombar = new QmlFragment({ - qml: "hifi/bottombar.qml" - }); - - bottombar.fromQml.connect(function(message) { - switch (message.method) { - case 'hide': - lowerBottomBar(); - break; - default: - print('[bottombar.js] Unrecognized message from bottomHud.qml:', JSON.stringify(message)); - } - }); - - avatarBtn = bottombar.addButton({ - icon: "icons/avatar-i.svg", - activeIcon: "icons/avatar-a.svg", - bgOpacity: 0, - height: 240, - width: 294, - hoverBgOpacity: 0, - activeBgOpacity: 0, - activeHoverBgOpacity: 0, - iconSize: 108, - textSize: 45, - text: "AVATAR" - }); - avatarBtn.clicked.connect(function() { - printd("Avatar button clicked"); - if (!avatarSelection.isVisible()) { - showAvatarSelection(); - } else { - hideAvatarSelection(); - } - }); - avatarSelection.onHidden = function() { - if (avatarBtn) { - avatarBtn.isActive = false; - } - }; - - gotoBtn = bottombar.addButton({ - icon: "icons/goto-i.svg", - activeIcon: "icons/goto-a.svg", - bgOpacity: 0, - hoverBgOpacity: 0, - activeBgOpacity: 0, - activeHoverBgOpacity: 0, - height: 240, - width: 294, - iconSize: 108, - textSize: 45, - text: "GO TO" - }); - - gotoBtn.clicked.connect(function() { - if (!gotoScript.isVisible()) { - showAddressBar(); - } else { - hideAddressBar(); - } - }); - - bubbleBtn = bottombar.addButton({ - icon: "icons/bubble-i.svg", - activeIcon: "icons/bubble-a.svg", - bgOpacity: 0, - hoverBgOpacity: 0, - activeBgOpacity: 0, - activeHoverBgOpacity: 0, - height: 240, - width: 294, - iconSize: 108, - textSize: 45, - text: "BUBBLE" - }); - - bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); - - bubbleBtn.clicked.connect(function() { - Users.toggleIgnoreRadius(); - bubbleBtn.editProperties({isActive: Users.getIgnoreRadiusEnabled()}); - }); - - loginBtn = bottombar.addButton({ - icon: "icons/login-i.svg", - activeIcon: "icons/login-a.svg", - height: 240, - width: 294, - iconSize: 108, - textSize: 45, - text: Account.isLoggedIn() ? "LOG OUT" : "LOG IN" - }); - loginBtn.clicked.connect(function() { - if (!Account.isLoggedIn()) { - Account.checkAndSignalForAccessToken(); - } else { - Menu.triggerOption("Login / Sign Up"); - } - }); - - // TODO: setup all the buttons or provide a dynamic interface - - raiseBottomBar(); - - -} - -var setupBottomHudOptionsBar = function() { - var bottomHud = new QmlFragment({ - qml: "hifi/bottomHudOptions.qml" - }); - - bottomHudOptionsBar = { - show: function() { - bottomHud.setVisible(true); - }, - hide: function() { - bottomHud.setVisible(false); - }, - qmlFragment: bottomHud - }; - bottomHud.fromQml.connect( - function(message) { - switch (message.method) { - case 'showUpBar': - printd('[bottombar.js] showUpBar message from bottomHudOptions.qml: ', JSON.stringify(message)); - raiseBottomBar(); - break; - default: - print('[bottombar.js] Unrecognized message from bottomHudOptions.qml:', JSON.stringify(message)); - } - } - ); -} - -function lowerBottomBar() { - if (bottombar) { - bottombar.setVisible(false); - } - if (bottomHudOptionsBar) { - bottomHudOptionsBar.show(); - } - Controller.setVPadExtraBottomMargin(0); -} - -function raiseBottomBar() { - print('[bottombar.js] raiseBottomBar begin'); - if (bottombar) { - bottombar.setVisible(true); - } - if (bottomHudOptionsBar) { - bottomHudOptionsBar.hide(); - } - Controller.setVPadExtraBottomMargin(255); // Height in bottombar.qml - print('[bottombar.js] raiseBottomBar end'); -} - -function showAddressBar() { - gotoScript.show(); - gotoBtn.isActive = true; -} - -function hideAddressBar() { - gotoScript.hide(); - gotoBtn.isActive = false; -} - -function showAvatarSelection() { - avatarSelection.show(); - avatarBtn.isActive = true; -} - -function hideAvatarSelection() { - avatarSelection.hide(); - avatarBtn.isActive = false; -} - -// TODO: Move to avatarSelection.js and make it possible to hide the window from there AND switch the button state here too -function processedNewAvatar(url, modelName) { - avatarSelection.refreshSelectedAvatar(url); - hideAvatarSelection(); -} - -function handleLogin() { - if (loginBtn) { - loginBtn.editProperties({text: "LOG OUT"}); - } -} - -function onUsernameChanged(username) { - if (Account.isLoggedIn()) { - MyAvatar.displayName = username; - } -} - -function handleLogout() { - MyAvatar.displayName = ""; - if (loginBtn) { - loginBtn.editProperties({text: "LOG IN"}); - } -} - -Script.scriptEnding.connect(function () { - shutdown(); - GlobalServices.connected.disconnect(handleLogin); - GlobalServices.disconnected.disconnect(handleLogout); - GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); -}); - -init(); - -}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android/goto.js b/scripts/system/+android/goto.js deleted file mode 100644 index 750844a2a4..0000000000 --- a/scripts/system/+android/goto.js +++ /dev/null @@ -1,108 +0,0 @@ -"use strict"; -// -// goto-android.js -// scripts/system/ -// -// Created by Gabriel Calero & Cristian Duarte on 12 Sep 2017 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var window; - - -var logEnabled = false; -function printd(str) { - if (logEnabled) - print("[goto-android.js] " + str); -} - -function init() { -} - -function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - switch (message.method) { - case 'shownChanged': - if (notifyShownChange) { - notifyShownChange(message.params.shown); - } ; - break; - case 'hide': - module.exports.hide(); - module.exports.onHidden(); - break; - case 'openAndroidActivity': - App.openAndroidActivity("Home"); - break; - default: - print('[goto-android.js] Unrecognized message from AddressBarDialog.qml:', JSON.stringify(message)); - } -} - -function sendToQml(message) { - window.sendToQml(message); -} - -var isVisible = false; -var qmlConnected = false; -var notifyShownChange; -module.exports = { - init: function() { - window = new QmlFragment({ - qml: "AddressBarDialog.qml", - visible: false - }); - }, - show: function() { - if (isVisible) return; - Controller.setVPadHidden(true); - if (window) { - if (!qmlConnected) { - window.fromQml.connect(fromQml); - qmlConnected = true; - } - window.setVisible(true); - isVisible = true; - } - }, - hide: function() { - if (!isVisible) return; - Controller.setVPadHidden(false); - if (window) { - if (qmlConnected) { - window.fromQml.disconnect(fromQml); - qmlConnected = false; - } - window.setVisible(false); - } - isVisible = false; - }, - destroy: function() { - if (window) { - window.close(); - window = null; - } - }, - isVisible: function() { - return isVisible; - }, - width: function() { - return window ? window.size.x : 0; - }, - height: function() { - return window ? window.size.y : 0; - }, - position: function() { - return window && isVisible ? window.position : null; - }, - setOnShownChange: function(f) { - notifyShownChange = f; - }, - onHidden: function() { } - - -}; - -init(); From ccb5e1e06c6627d8a109d5e2b72e8ce3e45e1298 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Mon, 23 Apr 2018 15:29:51 -0300 Subject: [PATCH 30/83] Commenting out display plugin activation/desactivation --- interface/src/Application.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d7f0db12c1..91712f9448 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7942,14 +7942,16 @@ void Application::openAndroidActivity(const QString& activityName) { void Application::enterBackground() { QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); - getActiveDisplayPlugin()->deactivate(); + //GC: commenting it out until we fix it + //getActiveDisplayPlugin()->deactivate(); } void Application::enterForeground() { QMetaObject::invokeMethod(DependencyManager::get().data(), "start", Qt::BlockingQueuedConnection); - if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { + //GC: commenting it out until we fix it + /*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { qWarning() << "Could not re-activate display plugin"; - } + }*/ } #endif From 165c3214a75df8e9496023d92086672bd9deedf9 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 24 Apr 2018 18:43:53 -0700 Subject: [PATCH 31/83] Avatar Scripts --- interface/src/Application.cpp | 29 +++++++++++++++++++ interface/src/avatar/MyAvatar.cpp | 13 +++++++++ interface/src/avatar/MyAvatar.h | 9 +++++- libraries/fbx/src/FBX.h | 1 + libraries/fbx/src/FSTReader.h | 1 + .../src/model-networking/ModelCache.cpp | 15 ++++++++++ 6 files changed, 67 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6d4c82d4bf..c1f77c792a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4724,6 +4724,35 @@ void Application::init() { avatar->setCollisionSound(sound); } }, Qt::QueuedConnection); + + connect(getMyAvatar().get(), &MyAvatar::avatarScriptsNeedToLoad, this, [this]() { + if (auto avatar = getMyAvatar()) { + auto scripts = avatar->getSkeletonModel()->getFBXGeometry().scripts; + if (scripts.size() > 0) { + auto scriptEngines = DependencyManager::get(); + auto runningScripts = scriptEngines->getRunningScripts(); + for (auto script : scripts) { + int index = runningScripts.indexOf(script.toString()); + if (index < 0) { + auto loaded = scriptEngines->loadScript(script); + avatar->addScriptToUnload(script); + } + } + } + } + }, Qt::QueuedConnection); + + connect(getMyAvatar().get(), &MyAvatar::avatarScriptsNeedToUnload, this, [this]() { + if (auto avatar = getMyAvatar()) { + auto scripts = avatar->getScriptsToUnload(); + if (scripts.size() > 0) { + auto scriptEngines = DependencyManager::get(); + for (auto script : scripts) { + scriptEngines->stopScript(script.toString(), false); + } + } + } + }, Qt::QueuedConnection); } void Application::updateLOD(float deltaTime) const { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 249a765d92..2ba4a6afca 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -121,6 +121,7 @@ MyAvatar::MyAvatar(QThread* thread) : _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::setURLFinished, this, &MyAvatar::setModelURLLoaded); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); @@ -1463,6 +1464,9 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + if (_scriptsToUnload.size() > 0) { + emit avatarScriptsNeedToUnload(); + } _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); @@ -2384,6 +2388,11 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings settings.endGroup(); } +void MyAvatar::setModelURLLoaded() { + _scriptsToUnload.clear(); + emit avatarScriptsNeedToLoad(); +} + void MyAvatar::leaveDomain() { clearScaleRestriction(); saveAvatarScale(); @@ -2831,6 +2840,10 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } +void MyAvatar::addScriptToUnload(QUrl& scriptUrl) { + _scriptsToUnload.push_back(scriptUrl); +} + void MyAvatar::setSprintMode(bool sprint) { _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6f82c7dfb9..38e189f92b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -594,6 +594,9 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; + void addScriptToUnload(QUrl& scriptUrl); + const QVector& getScriptsToUnload() const { return _scriptsToUnload; }; + public slots: void increaseSize(); void decreaseSize(); @@ -659,10 +662,12 @@ signals: void sensorToWorldScaleChanged(float sensorToWorldScale); void attachmentsChanged(); void scaleChanged(); + void avatarScriptsNeedToLoad(); + void avatarScriptsNeedToUnload(); private slots: void leaveDomain(); - + void setModelURLLoaded(); protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; @@ -905,6 +910,8 @@ private: // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; + + QVector _scriptsToUnload; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index a609d85fc8..d40511ce27 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -298,6 +298,7 @@ public: bool hasSkeletonJoints; QVector meshes; + QVector scripts; QHash materials; diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 981bae4feb..d1204c0876 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -28,6 +28,7 @@ static const QString TRANSLATION_Z_FIELD = "tz"; static const QString JOINT_FIELD = "joint"; static const QString FREE_JOINT_FIELD = "freeJoint"; static const QString BLENDSHAPE_FIELD = "bs"; +static const QString SCRIPT_FIELD = "script"; class FSTReader { public: diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index f17cdbb7e8..5a25bbc6fd 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -66,6 +66,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { auto mapping = FSTReader::readMapping(data); QString filename = mapping.value("filename").toString(); + if (filename.isNull()) { qCDebug(modelnetworking) << "Mapping file" << _url << "has no \"filename\" field"; finishedLoading(false); @@ -209,6 +210,20 @@ void GeometryReader::run() { throw QString("unsupported format"); } + if (_mapping.value("type").toString() == "body+head") { + auto scripts = _mapping.value("script"); + if (!scripts.isNull()) { + auto scriptsMap = scripts.toMap(); + auto count = scriptsMap.size(); + if (count > 0) { + for (auto &key : scriptsMap.keys()) { + auto scriptUrl = scriptsMap[key].toString(); + fbxGeometry->scripts.push_back(QUrl(scriptUrl)); + } + } + } + } + // Ensure the resource has not been deleted auto resource = _resource.toStrongRef(); if (!resource) { From d03fcb5ed8a50ac4ba1bf813f450945445bea62c Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 10 Apr 2018 19:06:27 -0700 Subject: [PATCH 32/83] desktop equip --- .../controllerModules/equipEntity.js | 117 +++++++++++++++++- scripts/system/controllers/grab.js | 2 +- 2 files changed, 115 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 252f6efa9e..33091696f3 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -21,6 +21,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; +var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}"; // Each overlayInfoSet describes a single equip hotspot. @@ -176,6 +177,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var TRIGGER_OFF_VALUE = 0.1; var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab var BUMPER_ON_VALUE = 0.5; + + var UNEQUIP_KEY = "u"; function getWearableData(props) { @@ -270,6 +273,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.shouldSendStart = false; this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; + this.mouseEquip = false; + this.mouseEquipAnimationHandler; this.parameters = makeDispatcherModuleParameters( 300, @@ -279,10 +284,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipHotspotBuddy = new EquipHotspotBuddy(); - this.setMessageGrabData = function(entityProperties) { + this.setMessageGrabData = function(entityProperties, mouseEquip) { if (entityProperties) { this.messageGrabEntity = true; this.grabEntityProps = entityProperties; + this.mouseEquip = mouseEquip; } }; @@ -552,6 +558,15 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // 100 ms seems to be sufficient time to force the check even occur after the object has been initialized. Script.setTimeout(grabEquipCheck, 100); } + + if (this.mouseEquip) { + this.removeMouseEquipAnimation(); + if (this.hand === RIGHT_HAND) { + this.mouseEquipAnimationHandler = MyAvatar.addAnimationStateHandler(this.rightHandMouseEquipAnimation, []); + } else { + this.mouseEquipAnimationHandler = MyAvatar.addAnimationStateHandler(this.leftHandMouseEquipAnimation, []); + } + } }; this.endEquipEntity = function () { @@ -574,6 +589,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; + + if (this.mouseEquip) { + this.removeMouseEquipAnimation(); + this.mouseEquip = false; + } }; this.updateInputs = function (controllerData) { @@ -650,7 +670,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.isTargetIDValid(controllerData)) { + if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -740,6 +760,40 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.endEquipEntity(); } }; + + this.removeMouseEquipAnimation = function() { + if (this.mouseEquipAnimationHandler) { + this.mouseEquipAnimationHandler = MyAvatar.removeAnimationStateHandler(this.mouseEquipAnimationHandler); + } + }; + + this.leftHandMouseEquipAnimation = function() { + var result = {}; + var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); + var leftShoulderPosition = MyAvatar.getJointPosition("LeftShoulder"); + var cameraToLeftShoulder = Vec3.subtract(leftShoulderPosition, Camera.position); + var cameraToLeftShoulderNormalized = Vec3.normalize(cameraToLeftShoulder); + var leftHandPositionNew = Vec3.sum(leftShoulderPosition, cameraToLeftShoulderNormalized); + var leftHandPositionNewAvatarFrame = Vec3.subtract(leftHandPositionNew, MyAvatar.position); + result.leftHandType = 1; + result.leftHandPosition = leftHandPositionNewAvatarFrame; + result.leftHandRotation = Quat.multiply(Quat.lookAtSimple(leftHandPositionNew, Camera.position), Quat.fromPitchYawRollDegrees(90, 0, -90)); + return result; + }; + + this.rightHandMouseEquipAnimation = function() { + var result = {}; + var rightHandPosition = MyAvatar.getJointPosition("RightHand"); + var rightShoulderPosition = MyAvatar.getJointPosition("RightShoulder"); + var cameraToRightShoulder = Vec3.subtract(rightShoulderPosition, Camera.position); + var cameraToRightShoulderNormalized = Vec3.normalize(cameraToRightShoulder); + var rightHandPositionNew = Vec3.sum(rightShoulderPosition, cameraToRightShoulderNormalized); + var rightHandPositionNewAvatarFrame = Vec3.subtract(rightHandPositionNew, MyAvatar.position); + result.rightHandType = 1; + result.rightHandPosition = rightHandPositionNewAvatarFrame; + result.rightHandRotation = Quat.multiply(Quat.lookAtSimple(rightHandPositionNew, Camera.position), Quat.fromPitchYawRollDegrees(90, 0, 90)); + return result; + }; } var handleMessage = function(channel, message, sender) { @@ -751,7 +805,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - equipModule.setMessageGrabData(entityProperties); + var mouseEquip = false; + equipModule.setMessageGrabData(entityProperties, mouseEquip); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); @@ -768,10 +823,63 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } }; + + var clearGrabActions = function(entityID) { + var actionIDs = Entities.getActionIDs(entityID); + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments.tag; + if (tag.slice(0, 5) == "grab-") { + Entities.deleteAction(entityID, actionID); + } + } + }; + + var onMousePress = function(event) { + if (isInEditMode()) { // ignore any mouse clicks on the entity while create/edit is open + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay, true); + if (intersection.intersects) { + var entityProperties = Entities.getEntityProperties(intersection.entityID, DISPATCHER_PROPERTIES); + if (entityProperties.parentID === EMPTY_PARENT_ID) { + entityProperties.id = intersection.entityID; + var rightHandPosition = MyAvatar.getJointPosition("RightHand"); + var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); + var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition); + var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition); + var leftHandAvailable = leftEquipEntity.targetEntityID === null; + var rightHandAvailable = rightEquipEntity.targetEntityID === null; + var mouseEquip = true; + if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { + clearGrabActions(intersection.entityID); + rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { + clearGrabActions(intersection.entityID); + leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + } + } + } + }; + + var onKeyPress = function(event) { + if (event.text === UNEQUIP_KEY) { + if (rightEquipEntity.mouseEquip) { + rightEquipEntity.endEquipEntity(); + } + if (leftEquipEntity.mouseEquip) { + leftEquipEntity.endEquipEntity(); + } + } + }; Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-Drop'); Messages.messageReceived.connect(handleMessage); + Controller.mousePressEvent.connect(onMousePress); + Controller.keyPressEvent.connect(onKeyPress); var leftEquipEntity = new EquipEntity(LEFT_HAND); var rightEquipEntity = new EquipEntity(RIGHT_HAND); @@ -785,6 +893,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa disableDispatcherModule("LeftEquipEntity"); disableDispatcherModule("RightEquipEntity"); clearAttachPoints(); + Messages.messageReceived.disconnect(handleMessage); + Controller.mousePressEvent.disconnect(onMousePress); + Controller.keyPressEvent.disconnect(onKeyPress); } Script.scriptEnding.connect(cleanup); }()); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 1171703847..b32c64d189 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -567,7 +567,7 @@ Grabber.prototype.moveEventProcess = function() { } if (!this.actionID) { - if (!entityIsGrabbedByOther(this.entityID)) { + if (!entityIsGrabbedByOther(this.entityID) ) && Entities.getEntityProperties(this.entityID, ['parentID']).parentID !== MyAvatar.SELF_ID) { this.actionID = Entities.addAction("far-grab", this.entityID, actionArgs); } } else { From de4aff630ade00fe38d379d34c7575740b7e2a3a Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 12 Apr 2018 12:41:36 -0700 Subject: [PATCH 33/83] fix grab.js change --- scripts/system/controllers/grab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index b32c64d189..4af2d97a8f 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -567,7 +567,7 @@ Grabber.prototype.moveEventProcess = function() { } if (!this.actionID) { - if (!entityIsGrabbedByOther(this.entityID) ) && Entities.getEntityProperties(this.entityID, ['parentID']).parentID !== MyAvatar.SELF_ID) { + if (!entityIsGrabbedByOther(this.entityID) && Entities.getEntityProperties(this.entityID, ['parentID']).parentID !== MyAvatar.SELF_ID) { this.actionID = Entities.addAction("far-grab", this.entityID, actionArgs); } } else { From 4446ef853f8c396171a4d51a4cec4b23fd8d880c Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 16 Apr 2018 12:56:22 -0700 Subject: [PATCH 34/83] remove animations --- .../controllerModules/equipEntity.js | 60 ++----------------- 1 file changed, 5 insertions(+), 55 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 33091696f3..7b9640047c 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -22,6 +22,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}"; +var UNEQUIP_KEY = "u"; // Each overlayInfoSet describes a single equip hotspot. @@ -176,10 +177,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_OFF_VALUE = 0.1; var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab - var BUMPER_ON_VALUE = 0.5; - - var UNEQUIP_KEY = "u"; - + var BUMPER_ON_VALUE = 0.5 + function getWearableData(props) { var wearable = {}; @@ -274,7 +273,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; this.mouseEquip = false; - this.mouseEquipAnimationHandler; this.parameters = makeDispatcherModuleParameters( 300, @@ -558,15 +556,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // 100 ms seems to be sufficient time to force the check even occur after the object has been initialized. Script.setTimeout(grabEquipCheck, 100); } - - if (this.mouseEquip) { - this.removeMouseEquipAnimation(); - if (this.hand === RIGHT_HAND) { - this.mouseEquipAnimationHandler = MyAvatar.addAnimationStateHandler(this.rightHandMouseEquipAnimation, []); - } else { - this.mouseEquipAnimationHandler = MyAvatar.addAnimationStateHandler(this.leftHandMouseEquipAnimation, []); - } - } }; this.endEquipEntity = function () { @@ -575,7 +564,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa parentID: Uuid.NULL, parentJointIndex: -1 }); - +; var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args); @@ -589,11 +578,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; - - if (this.mouseEquip) { - this.removeMouseEquipAnimation(); - this.mouseEquip = false; - } }; this.updateInputs = function (controllerData) { @@ -760,40 +744,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.endEquipEntity(); } }; - - this.removeMouseEquipAnimation = function() { - if (this.mouseEquipAnimationHandler) { - this.mouseEquipAnimationHandler = MyAvatar.removeAnimationStateHandler(this.mouseEquipAnimationHandler); - } - }; - - this.leftHandMouseEquipAnimation = function() { - var result = {}; - var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); - var leftShoulderPosition = MyAvatar.getJointPosition("LeftShoulder"); - var cameraToLeftShoulder = Vec3.subtract(leftShoulderPosition, Camera.position); - var cameraToLeftShoulderNormalized = Vec3.normalize(cameraToLeftShoulder); - var leftHandPositionNew = Vec3.sum(leftShoulderPosition, cameraToLeftShoulderNormalized); - var leftHandPositionNewAvatarFrame = Vec3.subtract(leftHandPositionNew, MyAvatar.position); - result.leftHandType = 1; - result.leftHandPosition = leftHandPositionNewAvatarFrame; - result.leftHandRotation = Quat.multiply(Quat.lookAtSimple(leftHandPositionNew, Camera.position), Quat.fromPitchYawRollDegrees(90, 0, -90)); - return result; - }; - - this.rightHandMouseEquipAnimation = function() { - var result = {}; - var rightHandPosition = MyAvatar.getJointPosition("RightHand"); - var rightShoulderPosition = MyAvatar.getJointPosition("RightShoulder"); - var cameraToRightShoulder = Vec3.subtract(rightShoulderPosition, Camera.position); - var cameraToRightShoulderNormalized = Vec3.normalize(cameraToRightShoulder); - var rightHandPositionNew = Vec3.sum(rightShoulderPosition, cameraToRightShoulderNormalized); - var rightHandPositionNewAvatarFrame = Vec3.subtract(rightHandPositionNew, MyAvatar.position); - result.rightHandType = 1; - result.rightHandPosition = rightHandPositionNewAvatarFrame; - result.rightHandRotation = Quat.multiply(Quat.lookAtSimple(rightHandPositionNew, Camera.position), Quat.fromPitchYawRollDegrees(90, 0, 90)); - return result; - }; } var handleMessage = function(channel, message, sender) { @@ -874,7 +824,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } }; - + Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-Drop'); Messages.messageReceived.connect(handleMessage); From 0f2679b725ec0ae28dbda35c2ec016688807b348 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 16 Apr 2018 13:12:33 -0700 Subject: [PATCH 35/83] pre PR adjustments --- .../controllerModules/equipEntity.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 7b9640047c..ccd0c750df 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -21,8 +21,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; -var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}"; -var UNEQUIP_KEY = "u"; // Each overlayInfoSet describes a single equip hotspot. @@ -177,9 +175,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_OFF_VALUE = 0.1; var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab - var BUMPER_ON_VALUE = 0.5 - + var BUMPER_ON_VALUE = 0.5; + + var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}"; + + var UNEQUIP_KEY = "u"; + function getWearableData(props) { var wearable = {}; try { @@ -564,7 +566,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa parentID: Uuid.NULL, parentJointIndex: -1 }); -; + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args); @@ -780,14 +782,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); var tag = actionArguments.tag; - if (tag.slice(0, 5) == "grab-") { + if (tag.slice(0, 5) === "grab-") { Entities.deleteAction(entityID, actionID); } } }; var onMousePress = function(event) { - if (isInEditMode()) { // ignore any mouse clicks on the entity while create/edit is open + if (isInEditMode()) { // don't consider any mouse clicks on the entity while in edit return; } var pickRay = Camera.computePickRay(event.x, event.y); @@ -804,9 +806,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var rightHandAvailable = rightEquipEntity.targetEntityID === null; var mouseEquip = true; if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { + // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(intersection.entityID); rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); - } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { + } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) + // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(intersection.entityID); leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); } @@ -824,7 +828,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } }; - + Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-Drop'); Messages.messageReceived.connect(handleMessage); From a869b58dfb3cfa525dba1ba1dd0112304f29bb28 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 16 Apr 2018 13:33:17 -0700 Subject: [PATCH 36/83] fix bracket --- scripts/system/controllers/controllerModules/equipEntity.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index ccd0c750df..00fc1e581a 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -181,7 +181,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var UNEQUIP_KEY = "u"; - function getWearableData(props) { var wearable = {}; try { @@ -809,7 +808,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(intersection.entityID); rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); - } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) + } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(intersection.entityID); leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); From 3a35fa1c08c7802e4af20466b1f4c54be2a239f1 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Apr 2018 16:13:10 -0700 Subject: [PATCH 37/83] missing reset flag on end equip, remove mouseEquip check on unequip via U --- .../system/controllers/controllerModules/equipEntity.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 00fc1e581a..4e960c37f1 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -579,6 +579,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; + this.mouseEquip = false; }; this.updateInputs = function (controllerData) { @@ -796,7 +797,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (intersection.intersects) { var entityProperties = Entities.getEntityProperties(intersection.entityID, DISPATCHER_PROPERTIES); if (entityProperties.parentID === EMPTY_PARENT_ID) { - entityProperties.id = intersection.entityID; + entityProperties.id = intersection.entityID; var rightHandPosition = MyAvatar.getJointPosition("RightHand"); var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition); @@ -819,10 +820,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var onKeyPress = function(event) { if (event.text === UNEQUIP_KEY) { - if (rightEquipEntity.mouseEquip) { + if (rightEquipEntity.targetEntityID) { rightEquipEntity.endEquipEntity(); } - if (leftEquipEntity.mouseEquip) { + if (leftEquipEntity.targetEntityID) { leftEquipEntity.endEquipEntity(); } } From 4a08a6c147a4ab29bde9af35edec8d151c7873e4 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Apr 2018 16:26:33 -0700 Subject: [PATCH 38/83] ensure target mouse entity has equip data --- scripts/system/controllers/controllerModules/equipEntity.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 4e960c37f1..6406b8593f 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -796,7 +796,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var intersection = Entities.findRayIntersection(pickRay, true); if (intersection.intersects) { var entityProperties = Entities.getEntityProperties(intersection.entityID, DISPATCHER_PROPERTIES); - if (entityProperties.parentID === EMPTY_PARENT_ID) { + var hasEquipData = getWearableData(entityProperties).joints || getEquipHotspotsData(entityProperties).length > 0; + if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID) { entityProperties.id = intersection.entityID; var rightHandPosition = MyAvatar.getJointPosition("RightHand"); var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); From 07fb604090795e837f348eb471b38d85b65251eb Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 18 Apr 2018 15:00:10 -0700 Subject: [PATCH 39/83] add entityIsEquipped utils for grab.js check --- scripts/system/controllers/grab.js | 2 +- scripts/system/libraries/controllerDispatcherUtils.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 4af2d97a8f..12cc1a2f7c 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -567,7 +567,7 @@ Grabber.prototype.moveEventProcess = function() { } if (!this.actionID) { - if (!entityIsGrabbedByOther(this.entityID) && Entities.getEntityProperties(this.entityID, ['parentID']).parentID !== MyAvatar.SELF_ID) { + if (!entityIsGrabbedByOther(this.entityID) && !entityIsEquipped(this.entityID)) { this.actionID = Entities.addAction("far-grab", this.entityID, actionArgs); } } else { diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 75e1d6668b..41b4458bc5 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -384,6 +384,14 @@ distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { return Vec3.distance(v, localPoint); }; +entityIsEquipped = function(entityID) { + var rightEquipEntity = getEnabledModuleByName("RightEquipEntity"); + var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity"); + var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false; + var equippedInLeftHand = leftEquipEntity ? leftEquipEntity.targetEntityID === entityID : false; + return equippedInRightHand || equippedInLeftHand; +}; + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, From 5bf48041df0c1dc8d0eecbb98b30bf1460346e3e Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 18 Apr 2018 15:01:14 -0700 Subject: [PATCH 40/83] tabs --- scripts/system/libraries/controllerDispatcherUtils.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 41b4458bc5..61b54ca156 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -385,11 +385,11 @@ distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { }; entityIsEquipped = function(entityID) { - var rightEquipEntity = getEnabledModuleByName("RightEquipEntity"); - var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity"); - var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false; - var equippedInLeftHand = leftEquipEntity ? leftEquipEntity.targetEntityID === entityID : false; - return equippedInRightHand || equippedInLeftHand; + var rightEquipEntity = getEnabledModuleByName("RightEquipEntity"); + var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity"); + var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false; + var equippedInLeftHand = leftEquipEntity ? leftEquipEntity.targetEntityID === entityID : false; + return equippedInRightHand || equippedInLeftHand; }; if (typeof module !== 'undefined') { From 6a3303fe3ead895273cfd7a596b5c8bb1b5dce44 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 18 Apr 2018 15:05:29 -0700 Subject: [PATCH 41/83] tabs --- scripts/system/controllers/controllerModules/equipEntity.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 6406b8593f..5807465abb 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -579,7 +579,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; - this.mouseEquip = false; + this.mouseEquip = false; }; this.updateInputs = function (controllerData) { @@ -796,7 +796,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var intersection = Entities.findRayIntersection(pickRay, true); if (intersection.intersects) { var entityProperties = Entities.getEntityProperties(intersection.entityID, DISPATCHER_PROPERTIES); - var hasEquipData = getWearableData(entityProperties).joints || getEquipHotspotsData(entityProperties).length > 0; + var hasEquipData = getWearableData(entityProperties).joints || getEquipHotspotsData(entityProperties).length > 0; if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID) { entityProperties.id = intersection.entityID; var rightHandPosition = MyAvatar.getJointPosition("RightHand"); From 3c441f0312b4559d12cde9c9242ad2d26238f118 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 19 Apr 2018 11:17:26 -0700 Subject: [PATCH 42/83] safer clear grab actions --- scripts/system/controllers/controllerModules/equipEntity.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 5807465abb..3134ec6736 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -778,11 +778,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var clearGrabActions = function(entityID) { var actionIDs = Entities.getActionIDs(entityID); + var myGrabTag = "grab-" + MyAvatar.sessionUUID; for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); var tag = actionArguments.tag; - if (tag.slice(0, 5) === "grab-") { + if (tag === myGrabTag) { Entities.deleteAction(entityID, actionID); } } From 86b64fe3ab2a8b83216d4ecd4de16fc4a5b6eaca Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 19 Apr 2018 16:29:49 -0700 Subject: [PATCH 43/83] fix mouse equipping while someone else far grabbing --- .../controllerModules/equipEntity.js | 11 +++++----- .../libraries/controllerDispatcherUtils.js | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 3134ec6736..0dca6f11f4 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -796,10 +796,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); if (intersection.intersects) { - var entityProperties = Entities.getEntityProperties(intersection.entityID, DISPATCHER_PROPERTIES); + var entityID = intersection.entityID; + var entityProperties = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); var hasEquipData = getWearableData(entityProperties).joints || getEquipHotspotsData(entityProperties).length > 0; - if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID) { - entityProperties.id = intersection.entityID; + if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID && !entityIsFarGrabbedByOther(entityID)) { + entityProperties.id = entityID; var rightHandPosition = MyAvatar.getJointPosition("RightHand"); var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition); @@ -809,11 +810,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var mouseEquip = true; if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) - clearGrabActions(intersection.entityID); + clearGrabActions(entityID); rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) - clearGrabActions(intersection.entityID); + clearGrabActions(entityID); leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); } } diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 61b54ca156..a8a7c9da2c 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -392,6 +392,26 @@ entityIsEquipped = function(entityID) { return equippedInRightHand || equippedInLeftHand; }; +entityIsFarGrabbedByOther = function(entityID) { + // by convention, a far grab sets the tag of its action to be far-grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + var myFarGrabTag = "far-grab-" + MyAvatar.sessionUUID; + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments.tag; + if (tag == myFarGrabTag) { + // we see a far-grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 9) == "far-grab-") { + // we see a far-grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +}; + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, From f30e34e8648ba02b33c5616848f75bc15b590518 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 Apr 2018 18:18:19 -0700 Subject: [PATCH 44/83] update serverless content with uncompressed KTX skyboxes --- cmake/externals/serverless-content/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index a08b589ec2..3514260840 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v4.zip - URL_MD5 d4f42f630986c83427ff39e1fe9908c6 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v5.zip + URL_MD5 4e1837bdbf0ee053b413ac92adce94b5 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From e0770f06b1eb27fcc5a8bd8db34ffb145bf0b3c5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 25 Apr 2018 19:35:26 -0700 Subject: [PATCH 45/83] Add scripts to Model Packager --- interface/src/Application.cpp | 48 +++++++++---------- interface/src/Application.h | 3 ++ interface/src/ModelPackager.cpp | 30 ++++++++++-- interface/src/ModelPackager.h | 2 + interface/src/ModelPropertiesDialog.cpp | 19 ++++++++ interface/src/ModelPropertiesDialog.h | 2 + interface/src/avatar/MyAvatar.cpp | 20 ++++---- interface/src/avatar/MyAvatar.h | 9 ++-- libraries/fbx/src/FBX.h | 2 +- libraries/fbx/src/FSTReader.cpp | 4 +- .../src/model-networking/ModelCache.cpp | 23 +++++---- 11 files changed, 105 insertions(+), 57 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c1f77c792a..ad4cb56703 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2285,7 +2285,11 @@ void Application::onAboutToQuit() { // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get()->hide("RunningScripts"); - + if (auto avatar = getMyAvatar()) { + auto urls = avatar->getScriptsToUnload(); + unloadAvatarScripts(urls); + } + _aboutToQuit = true; cleanupBeforeQuit(); @@ -4724,35 +4728,31 @@ void Application::init() { avatar->setCollisionSound(sound); } }, Qt::QueuedConnection); +} - connect(getMyAvatar().get(), &MyAvatar::avatarScriptsNeedToLoad, this, [this]() { - if (auto avatar = getMyAvatar()) { - auto scripts = avatar->getSkeletonModel()->getFBXGeometry().scripts; - if (scripts.size() > 0) { - auto scriptEngines = DependencyManager::get(); - auto runningScripts = scriptEngines->getRunningScripts(); - for (auto script : scripts) { - int index = runningScripts.indexOf(script.toString()); - if (index < 0) { - auto loaded = scriptEngines->loadScript(script); - avatar->addScriptToUnload(script); - } +void Application::loadAvatarScripts(const QVector& urls) { + if (auto avatar = getMyAvatar()) { + if (urls.size() > 0) { + auto scriptEngines = DependencyManager::get(); + auto runningScripts = scriptEngines->getRunningScripts(); + for (auto url : urls) { + int index = runningScripts.indexOf(url); + if (index < 0) { + scriptEngines->loadScript(url); + avatar->addScriptToUnload(url); } } } - }, Qt::QueuedConnection); + } +} - connect(getMyAvatar().get(), &MyAvatar::avatarScriptsNeedToUnload, this, [this]() { - if (auto avatar = getMyAvatar()) { - auto scripts = avatar->getScriptsToUnload(); - if (scripts.size() > 0) { - auto scriptEngines = DependencyManager::get(); - for (auto script : scripts) { - scriptEngines->stopScript(script.toString(), false); - } - } +void Application::unloadAvatarScripts(const QVector& urls) { + if (urls.size() > 0) { + auto scriptEngines = DependencyManager::get(); + for (auto url : urls) { + scriptEngines->stopScript(url, false); } - }, Qt::QueuedConnection); + } } void Application::updateLOD(float deltaTime) const { diff --git a/interface/src/Application.h b/interface/src/Application.h index 74b0e5a110..2e91f842a4 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -290,6 +290,9 @@ public: void replaceDomainContent(const QString& url); + void loadAvatarScripts(const QVector& urls); + void unloadAvatarScripts(const QVector& urls); + signals: void svoImportRequested(const QString& url); diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 5f4c7526e0..a87669bf47 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -156,9 +156,11 @@ bool ModelPackager::zipModel() { QByteArray nameField = _mapping.value(NAME_FIELD).toByteArray(); tempDir.mkpath(nameField + "/textures"); + tempDir.mkpath(nameField + "/scripts"); QDir fbxDir(tempDir.path() + "/" + nameField); QDir texDir(fbxDir.path() + "/textures"); - + QDir scriptDir(fbxDir.path() + "/scripts"); + // Copy textures listTextures(); if (!_textures.empty()) { @@ -166,6 +168,22 @@ bool ModelPackager::zipModel() { _texDir = _modelFile.path() + "/" + texdirField; copyTextures(_texDir, texDir); } + + // Copy scripts + QByteArray scriptField = _mapping.value(SCRIPT_FIELD).toByteArray(); + _mapping.remove(SCRIPT_FIELD); + if (scriptField.size() > 1) { + tempDir.mkpath(nameField + "/scripts"); + _scriptDir = _modelFile.path() + "/" + scriptField; + QDir wdir = QDir(_scriptDir); + _mapping.remove(SCRIPT_FIELD); + auto list = wdir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries); + for (auto script : list) { + auto sc = tempDir.relativeFilePath(scriptDir.path()) + "/" + QUrl(script).fileName(); + _mapping.insertMulti(SCRIPT_FIELD, sc); + } + copyDirectoryContent(wdir, scriptDir); + } // Copy LODs QVariantHash lodField = _mapping.value(LOD_FIELD).toHash(); @@ -189,7 +207,11 @@ bool ModelPackager::zipModel() { // Correct FST _mapping[FILENAME_FIELD] = tempDir.relativeFilePath(newPath); _mapping[TEXDIR_FIELD] = tempDir.relativeFilePath(texDir.path()); - + + for (auto multi : _mapping.values(SCRIPT_FIELD)) { + + multi.fromValue(tempDir.relativeFilePath(scriptDir.path()) + multi.toString()); + } // Copy FST QFile fst(tempDir.path() + "/" + nameField + ".fst"); if (fst.open(QIODevice::WriteOnly)) { @@ -237,7 +259,9 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename if (!mapping.contains(TEXDIR_FIELD)) { mapping.insert(TEXDIR_FIELD, "."); } - + if (!mapping.contains(SCRIPT_FIELD)) { + mapping.insert(SCRIPT_FIELD, "."); + } // mixamo/autodesk defaults if (!mapping.contains(SCALE_FIELD)) { mapping.insert(SCALE_FIELD, 1.0); diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index 10942833f9..60b3825c4d 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -37,10 +37,12 @@ private: QFileInfo _fbxInfo; FSTReader::ModelType _modelType; QString _texDir; + QString _scriptDir; QVariantHash _mapping; std::unique_ptr _geometry; QStringList _textures; + QStringList _scripts; }; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index ae352974ae..35b07aa2b2 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -43,6 +43,9 @@ _geometry(geometry) form->addRow("Texture Directory:", _textureDirectory = new QPushButton()); connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory())); + form->addRow("Script Directory:", _scriptDirectory = new QPushButton()); + connect(_scriptDirectory, SIGNAL(clicked(bool)), SLOT(chooseScriptDirectory())); + form->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setMaximum(FLT_MAX); _scale->setSingleStep(0.01); @@ -100,6 +103,7 @@ QVariantHash ModelPropertiesDialog::getMapping() const { mapping.insert(TYPE_FIELD, getType()); mapping.insert(NAME_FIELD, _name->text()); mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); + mapping.insert(SCRIPT_FIELD, _scriptDirectory->text()); mapping.insert(SCALE_FIELD, QString::number(_scale->value())); // update the joint indices @@ -157,6 +161,7 @@ void ModelPropertiesDialog::reset() { _name->setText(_originalMapping.value(NAME_FIELD).toString()); _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); + _scriptDirectory->setText(_originalMapping.value(SCRIPT_FIELD).toString()); QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); @@ -207,6 +212,20 @@ void ModelPropertiesDialog::chooseTextureDirectory() { _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); } +void ModelPropertiesDialog::chooseScriptDirectory() { + QString directory = QFileDialog::getExistingDirectory(this, "Choose Script Directory", + _basePath + "/" + _scriptDirectory->text()); + if (directory.isEmpty()) { + return; + } + if (!directory.startsWith(_basePath)) { + OffscreenUi::asyncWarning(NULL, "Invalid script directory", "Script directory must be child of base path."); + return; + } + _scriptDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); +} + + void ModelPropertiesDialog::updatePivotJoint() { _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); } diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index 11abc5ab54..e3c2d8ed6a 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -37,6 +37,7 @@ public: private slots: void reset(); void chooseTextureDirectory(); + void chooseScriptDirectory(); void updatePivotJoint(); void createNewFreeJoint(const QString& joint = QString()); @@ -52,6 +53,7 @@ private: FBXGeometry _geometry; QLineEdit* _name = nullptr; QPushButton* _textureDirectory = nullptr; + QPushButton* _scriptDirectory = nullptr; QDoubleSpinBox* _scale = nullptr; QDoubleSpinBox* _translationX = nullptr; QDoubleSpinBox* _translationY = nullptr; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2ba4a6afca..69acf26477 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -121,7 +121,12 @@ MyAvatar::MyAvatar(QThread* thread) : _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); - connect(_skeletonModel.get(), &Model::setURLFinished, this, &MyAvatar::setModelURLLoaded); + connect(_skeletonModel.get(), &Model::setURLFinished, this, [this](bool success) { + if (success) { + auto geometry = getSkeletonModel()->getFBXGeometry(); + qApp->loadAvatarScripts(geometry.scripts); + } + }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); @@ -1464,9 +1469,7 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { - if (_scriptsToUnload.size() > 0) { - emit avatarScriptsNeedToUnload(); - } + qApp->unloadAvatarScripts(_scriptsToUnload); _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); @@ -2388,11 +2391,6 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings settings.endGroup(); } -void MyAvatar::setModelURLLoaded() { - _scriptsToUnload.clear(); - emit avatarScriptsNeedToLoad(); -} - void MyAvatar::leaveDomain() { clearScaleRestriction(); saveAvatarScale(); @@ -2840,8 +2838,8 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } -void MyAvatar::addScriptToUnload(QUrl& scriptUrl) { - _scriptsToUnload.push_back(scriptUrl); +void MyAvatar::addScriptToUnload(QString& url) { + _scriptsToUnload.push_back(url); } void MyAvatar::setSprintMode(bool sprint) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 38e189f92b..6e67defe6f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -594,8 +594,8 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; - void addScriptToUnload(QUrl& scriptUrl); - const QVector& getScriptsToUnload() const { return _scriptsToUnload; }; + void addScriptToUnload(QString& url); + const QVector& getScriptsToUnload() const { return _scriptsToUnload; }; public slots: void increaseSize(); @@ -662,12 +662,9 @@ signals: void sensorToWorldScaleChanged(float sensorToWorldScale); void attachmentsChanged(); void scaleChanged(); - void avatarScriptsNeedToLoad(); - void avatarScriptsNeedToUnload(); private slots: void leaveDomain(); - void setModelURLLoaded(); protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; @@ -911,7 +908,7 @@ private: ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; - QVector _scriptsToUnload; + QVector _scriptsToUnload; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index d40511ce27..ce3fc52c3a 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -298,7 +298,7 @@ public: bool hasSkeletonJoints; QVector meshes; - QVector scripts; + QVector scripts; QHash materials; diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index cc4a919445..603f214c3e 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -84,7 +84,7 @@ void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD - << TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD + << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << FREE_JOINT_FIELD << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; buffer.open(QIODevice::WriteOnly); @@ -92,7 +92,7 @@ QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { for (auto key : PREFERED_ORDER) { auto it = mapping.find(key); if (it != mapping.constEnd()) { - if (key == FREE_JOINT_FIELD) { // writeVariant does not handle strings added using insertMulti. + if (key == FREE_JOINT_FIELD || key == SCRIPT_FIELD) { // writeVariant does not handle strings added using insertMulti. for (auto multi : mapping.values(key)) { buffer.write(key.toUtf8()); buffer.write(" = "); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 5a25bbc6fd..e3f543c403 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -210,16 +210,19 @@ void GeometryReader::run() { throw QString("unsupported format"); } - if (_mapping.value("type").toString() == "body+head") { - auto scripts = _mapping.value("script"); - if (!scripts.isNull()) { - auto scriptsMap = scripts.toMap(); - auto count = scriptsMap.size(); - if (count > 0) { - for (auto &key : scriptsMap.keys()) { - auto scriptUrl = scriptsMap[key].toString(); - fbxGeometry->scripts.push_back(QUrl(scriptUrl)); - } + // Store fst scripts on geometry + if (!_mapping.value(SCRIPT_FIELD).isNull()) { + QVariantList scripts = _mapping.values(SCRIPT_FIELD); + if (scripts.size() > 0) { + for (auto &script : scripts) { + QString scriptUrl = script.toString(); + if (QUrl(scriptUrl).isRelative()) { + if (scriptUrl.at(0) == '/') { + scriptUrl = scriptUrl.right(scriptUrl.length() - 1); + } + scriptUrl = _url.resolved(QUrl(scriptUrl)).toString(); + } + fbxGeometry->scripts.push_back(scriptUrl); } } } From 12f578c93c825feabf420b9f8548b597ad7b69d0 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 25 Apr 2018 19:46:31 -0700 Subject: [PATCH 46/83] More fixes --- interface/src/Application.cpp | 18 ++++++++---------- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/avatar/MyAvatar.h | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ad4cb56703..c38bb9295a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4731,16 +4731,14 @@ void Application::init() { } void Application::loadAvatarScripts(const QVector& urls) { - if (auto avatar = getMyAvatar()) { - if (urls.size() > 0) { - auto scriptEngines = DependencyManager::get(); - auto runningScripts = scriptEngines->getRunningScripts(); - for (auto url : urls) { - int index = runningScripts.indexOf(url); - if (index < 0) { - scriptEngines->loadScript(url); - avatar->addScriptToUnload(url); - } + if (urls.size() > 0) { + auto scriptEngines = DependencyManager::get(); + auto runningScripts = scriptEngines->getRunningScripts(); + for (auto url : urls) { + int index = runningScripts.indexOf(url); + if (index < 0) { + scriptEngines->loadScript(url); + getMyAvatar()->addScriptToUnload(url); } } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 69acf26477..462dbebbbb 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2838,7 +2838,7 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } -void MyAvatar::addScriptToUnload(QString& url) { +void MyAvatar::addScriptToUnload(const QString& url) { _scriptsToUnload.push_back(url); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6e67defe6f..00cb50e079 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -594,7 +594,7 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; - void addScriptToUnload(QString& url); + void addScriptToUnload(const QString& url); const QVector& getScriptsToUnload() const { return _scriptsToUnload; }; public slots: From 03e03727dbfa7ca30996419821faf7f65f6a1678 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 26 Apr 2018 12:55:28 -0700 Subject: [PATCH 47/83] fix bug: interface sends too many updates on settle --- libraries/physics/src/EntityMotionState.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index c2bacd4949..68f21eea87 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -447,7 +447,12 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); - if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { + // shouldSendUpdate() sould NOT be triggering updates to maintain the queryAACube of dynamic entities. + // The server is supposed to predict the transform of such moving things. The client performs a "double prediction" + // where it predicts what the the server is doing, and only sends updates whent the entity's true transform + // differs significantly. That is what the remoteSimulationOutOfSync() logic is all about. + if (_entity->dynamicDataNeedsTransmit() || + (!_entity->getDynamic() && _entity->queryAACubeNeedsUpdate())) { return true; } From c8d7dc9b915c6217ba3931c69753a5c96136491c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 26 Apr 2018 11:38:50 -0700 Subject: [PATCH 48/83] hide default port from addresses, fix localhost scheme --- libraries/networking/src/AddressManager.cpp | 10 ++++++---- libraries/networking/src/DomainHandler.cpp | 15 +++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index daebcfb40d..72bca8ba3b 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -329,12 +329,14 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { return false; } +static const QString LOCALHOST = "localhost"; + bool isPossiblePlaceName(QString possiblePlaceName) { bool result { false }; int length = possiblePlaceName.length(); static const int MINIMUM_PLACENAME_LENGTH = 1; static const int MAXIMUM_PLACENAME_LENGTH = 64; - if (possiblePlaceName.toLower() != "localhost" && + if (possiblePlaceName.toLower() != LOCALHOST && length >= MINIMUM_PLACENAME_LENGTH && length <= MAXIMUM_PLACENAME_LENGTH) { const QRegExp PLACE_NAME_REGEX = QRegExp("^[0-9A-Za-z](([0-9A-Za-z]|-(?!-))*[^\\W_]$|$)"); result = PLACE_NAME_REGEX.indexIn(possiblePlaceName) == 0; @@ -354,7 +356,7 @@ void AddressManager::handleLookupString(const QString& lookupString, bool fromSu sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX); lookupURL = QUrl(sanitizedString); - if (lookupURL.scheme().isEmpty()) { + if (lookupURL.scheme().isEmpty() || lookupURL.scheme().toLower() == LOCALHOST) { lookupURL = QUrl("hifi://" + sanitizedString); } } else { @@ -603,7 +605,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri if (ipAddressRegex.indexIn(lookupString) != -1) { QString domainIPString = ipAddressRegex.cap(1); - quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; + quint16 domainPort = 0; if (!ipAddressRegex.cap(2).isEmpty()) { domainPort = (quint16) ipAddressRegex.cap(2).toInt(); } @@ -625,7 +627,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri if (hostnameRegex.indexIn(lookupString) != -1) { QString domainHostname = hostnameRegex.cap(1); - quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; + quint16 domainPort = 0; if (!hostnameRegex.cap(2).isEmpty()) { domainPort = (quint16)hostnameRegex.cap(2).toInt(); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cd8064c4c0..871dc26899 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -166,7 +166,12 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { } } - if (_domainURL != domainURL || _sockAddr.getPort() != domainURL.port()) { + auto domainPort = domainURL.port(); + if (domainPort == -1) { + domainPort = DEFAULT_DOMAIN_SERVER_PORT; + } + + if (_domainURL != domainURL || _sockAddr.getPort() != domainPort) { // re-set the domain info so that auth information is reloaded hardReset(); @@ -192,12 +197,10 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { emit domainURLChanged(_domainURL); - if (_sockAddr.getPort() != domainURL.port()) { - qCDebug(networking) << "Updated domain port to" << domainURL.port(); + if (_sockAddr.getPort() != domainPort) { + qCDebug(networking) << "Updated domain port to" << domainPort; + _sockAddr.setPort(domainPort); } - - // grab the port by reading the string after the colon - _sockAddr.setPort(domainURL.port()); } } From d87adcec92da330476ec3529fe561aeeb8167c86 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 26 Apr 2018 17:47:12 -0700 Subject: [PATCH 49/83] make home go to DEFAULT_HIFI_ADDRESS by default --- interface/src/ui/AddressBarDialog.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 87bf09a252..789a2a2bdf 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -58,9 +58,8 @@ void AddressBarDialog::loadHome() { qDebug() << "Called LoadHome"; auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); - const QString DEFAULT_HOME_LOCATION = "localhost"; if (homeLocation == "") { - homeLocation = DEFAULT_HOME_LOCATION; + homeLocation = DEFAULT_HIFI_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } From 2662dc9b4e5ba665556ed60d791b3803ff5eb18f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:03:48 -0700 Subject: [PATCH 50/83] Suppress tracing log spam when tracing is not active --- libraries/shared/src/Trace.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index 93e2c6c4c2..1e1326968f 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -102,6 +102,9 @@ private: }; inline void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { + if (!DependencyManager::isSet()) { + return; + } const auto& tracer = DependencyManager::get(); if (tracer) { tracer->traceEvent(category, name, type, id, args, extra); From 4db83230ce94b966efdfe07553d368be68a6e893 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:09:31 -0700 Subject: [PATCH 51/83] Fix bad virtual cast on qml surface destruction --- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 23 +++++++++++++-------- libraries/ui/src/ui/OffscreenQmlSurface.h | 4 +++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 43b573a169..b3c1c486e9 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -223,6 +223,17 @@ void AudioHandler::run() { qDebug() << "QML Audio changed to " << _newTargetDevice; } +OffscreenQmlSurface::~OffscreenQmlSurface() { + clearFocusItem(); +} + +void OffscreenQmlSurface::clearFocusItem() { + if (_currentFocusItem) { + disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); + } + _currentFocusItem = nullptr; +} + void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); new QQmlFileSelector(engine); @@ -545,17 +556,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } void OffscreenQmlSurface::focusDestroyed(QObject* obj) { - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); - } - _currentFocusItem = nullptr; + clearFocusItem(); } void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + clearFocusItem(); + QQuickItem* item = static_cast(object); if (!item) { setFocusText(false); - _currentFocusItem = nullptr; return; } @@ -563,10 +572,6 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { qApp->sendEvent(object, &query); setFocusText(query.value(Qt::ImEnabled).toBool()); - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, 0); - } - // Raise and lower keyboard for QML text fields. // HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files. const char* READ_ONLY_PROPERTY = "readOnly"; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 9fa86f12a3..b95a8f117d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -22,7 +22,8 @@ class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: - + ~OffscreenQmlSurface(); + static void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -58,6 +59,7 @@ public slots: void sendToQml(const QVariant& message); protected: + void clearFocusItem(); void setFocusText(bool newFocusText); void initializeEngine(QQmlEngine* engine) override; void onRootContextCreated(QQmlContext* qmlContext) override; From 6640313256e0fadc6401628f6e0316518a01119a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:10:22 -0700 Subject: [PATCH 52/83] Fix memory leak in QML surfaces --- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index b3c1c486e9..0d8e22cebb 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -115,6 +115,7 @@ private: class UrlHandler : public QObject { Q_OBJECT public: + UrlHandler(QObject* parent = nullptr) : QObject(parent) {} Q_INVOKABLE bool canHandleUrl(const QString& url) { static auto handler = dynamic_cast(qApp); return handler && handler->canAcceptURL(url); @@ -257,7 +258,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { auto rootContext = engine->rootContext(); rootContext->setContextProperty("GL", ::getGLContextData()); - rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("urlHandler", new UrlHandler(rootContext)); rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); rootContext->setContextProperty("ApplicationInterface", qApp); auto javaScriptToInject = getEventBridgeJavascript(); From 1b612d373f845ea4dab1698fb36b12d5814d88ff Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:11:11 -0700 Subject: [PATCH 53/83] Explicitly delete QML context before releasing engine --- libraries/qml/src/qml/impl/SharedObject.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 9253c41b39..7bcb216ba8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -97,12 +97,13 @@ SharedObject::~SharedObject() { } // _rootItem is parented to the quickWindow, so needs no explicit destruction - //if (_rootItem) { - // delete _rootItem; - // _rootItem = nullptr; - //} - releaseEngine(_qmlContext->engine()); + if (_qmlContext) { + auto engine = _qmlContext->engine(); + delete _qmlContext; + _qmlContext = nullptr; + releaseEngine(engine); + } } void SharedObject::create(OffscreenSurface* surface) { @@ -210,9 +211,9 @@ QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { if (!globalEngine) { Q_ASSERT(0 == globalEngineRefCount); globalEngine = new QQmlEngine(); - surface->initializeQmlEngine(result); - ++globalEngineRefCount; + surface->initializeEngine(result); } + ++globalEngineRefCount; result = globalEngine; #else result = new QQmlEngine(); From e892694bf5bdcf56e5892a71fe7fb2492f7d42ec Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:13:35 -0700 Subject: [PATCH 54/83] Don't modify URL on web entity on QML shutdown, remove tablet related code from web entity --- .../src/RenderableWebEntityItem.cpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f333e805ce..7ad74d1eee 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -308,12 +308,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { - if (item && item->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - }); + _webSurface->load(_lastSourceUrl); } _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -330,16 +325,6 @@ void WebEntityRenderer::destroyWebSurface() { if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); - // Explicitly set the web URL to an empty string, in an effort to get a - // faster shutdown of any chromium processes interacting with audio - if (rootItem && _contentType == ContentType::HtmlContent) { - rootItem->setProperty(URL_PROPERTY, ""); - } - - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. From f2172d84a00d8add357029821c45f4d74d15d412 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:35:20 -0700 Subject: [PATCH 55/83] Add stress test for QML web content --- tests/qml/qml/controls/WebEntityView.qml | 32 ++++ tests/qml/src/main.cpp | 198 ++++++++++++++++++----- 2 files changed, 187 insertions(+), 43 deletions(-) create mode 100644 tests/qml/qml/controls/WebEntityView.qml diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml new file mode 100644 index 0000000000..a6c38f4abd --- /dev/null +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -0,0 +1,32 @@ +// +// WebEntityView.qml +// +// Created by Kunal Gosar on 16 March 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.5 + +WebEngineView { + id: webViewCore + objectName: "webEngineView" + width: parent !== null ? parent.width : undefined + height: parent !== null ? parent.height : undefined + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + //disable popup + onContextMenuRequested: { + request.accepted = true; + } + + onNewViewRequested: { + newViewRequestedCallback(request) + } +} diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 022f7290f4..c23a958da7 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -28,52 +28,82 @@ #include #include #include - +#include #include #include +#include + #include -#include #include #include #include #include #include #include +#include +#include + +#pragma optimize("", off) + +namespace gl { + extern void initModuleGl(); +} -class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { +QUrl getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +using QmlPtr = QSharedPointer; +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; }; class TestWindow : public QWindow { - public: TestWindow(); - private: - using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - OffscreenQmlSurface _offscreenQml; + std::array, DIVISIONS_X> _surfaces; + QOpenGLFunctions_4_5_Core _glf; - uint32_t _currentTexture{ 0 }; - GLsync _readFence{ 0 }; std::function _discardLamdba; QSize _size; + size_t _surfaceCount{ 0 }; GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; void initGl(); + void updateSurfaces(); + void buildSurface(QmlInfo& qmlInfo); + void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; TestWindow::TestWindow() { - setSurfaceType(QSurface::OpenGLSurface); + Setting::init(); + setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); @@ -85,11 +115,12 @@ TestWindow::TestWindow() { show(); + resize(QSize(800, 600)); auto timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); - timer->setInterval(5); + timer->setInterval(30); connect(timer, &QTimer::timeout, [&] { draw(); }); timer->start(); @@ -97,7 +128,6 @@ TestWindow::TestWindow() { timer->stop(); _aboutToQuit = true; }); - } void TestWindow::initGl() { @@ -105,6 +135,7 @@ void TestWindow::initGl() { if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::initModuleGl(); _glf.initializeOpenGLFunctions(); _glf.glCreateFramebuffers(1, &_fbo); @@ -113,15 +144,104 @@ void TestWindow::initGl() { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = _offscreenQml.getDiscardLambda(); - _offscreenQml.resize({ 640, 480 }); - _offscreenQml.load(QUrl::fromLocalFile("C:/Users/bdavi/Git/hifi/tests/qml/qml/main.qml")); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } +static const int DEFAULT_MAX_FPS = 10; +static const int YOUTUBE_MAX_FPS = 30; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString getSourceUrl() { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + //"https://www.youtube.com/watch?v=gDXwhHm4GhM", + //"https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + auto index = rand() % SOURCE_URLS.size(); + return SOURCE_URLS[index]; +} + + + +void TestWindow::buildSurface(QmlInfo& qmlInfo) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(2.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + auto& surface = qmlInfo.surface; + surface.reset(new hifi::qml::OffscreenSurface()); + surface->setMaxFps(DEFAULT_MAX_FPS); + surface->resize(_qmlSize); + surface->setMaxFps(DEFAULT_MAX_FPS); + hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl()); + }; + surface->load(getTestResource(CONTROL_URL), callback); + surface->resume(); +} + +void TestWindow::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + QQuickItem* rootItem = surface->getRootItem(); + if (rootItem) { + QObject* obj = rootItem->findChild("webEngineView"); + if (!obj && rootItem->objectName() == "webEngineView") { + obj = rootItem; + } + if (obj) { + // stop loading + QMetaObject::invokeMethod(obj, "stop"); + } + } + surface->pause(); + surface.reset(); +} + +void TestWindow::updateSurfaces() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (randFloat() > 0.99f) { + buildSurface(qmlInfo); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + void TestWindow::draw() { if (_aboutToQuit) { return; @@ -140,36 +260,31 @@ void TestWindow::draw() { return; } + updateSurfaces(); + + auto size = this->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - TextureAndFence newTextureAndFence; - if (_offscreenQml.fetchTexture(newTextureAndFence)) { - if (_currentTexture) { - _discardLamdba(_currentTexture, _readFence); - _readFence = 0; + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, qmlInfo.texture, 0); + _glf.glBlitNamedFramebuffer(_fbo, 0, + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); } - - _currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, _currentTexture, 0); } - - auto diff = _size - _qmlSize; - diff /= 2; - auto qmlExtent = diff + _qmlSize; - - if (_currentTexture) { - _glf.glBlitNamedFramebuffer(_fbo, 0, - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - diff.width(), diff.height(), qmlExtent.width() - 1, qmlExtent.height() - 2, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - - if (_readFence) { - _glf.glDeleteSync(_readFence); - } - _readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); _glf.glFlush(); _glContext.swapBuffers(this); @@ -180,11 +295,8 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { } int main(int argc, char** argv) { - setupHifiApplication("QML Test"); - QGuiApplication app(argc, argv); TestWindow window; app.exec(); return 0; } - From c163ae63b8954439cebe2d11c8d8902e807ef910 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 27 Apr 2018 10:20:55 -0700 Subject: [PATCH 56/83] Allow gifting of Pending items; remove PoP verification rescheduling on Pending items --- .../hifi/commerce/purchases/PurchasedItem.qml | 1 - libraries/entities/src/EntityTree.cpp | 33 ++----------------- libraries/entities/src/EntityTree.h | 3 +- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 7d7a882ee0..4db98091c1 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -239,7 +239,6 @@ Item { width: 62; onLoaded: { - item.enabled = (root.purchaseStatus === "confirmed"); item.buttonGlyphText = hifi.glyphs.gift; item.buttonText = "Gift"; item.buttonClicked = function() { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d391dc8be1..b56f367e0a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1172,16 +1172,6 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) _challengeOwnershipTimeoutTimer->start(5000); } -void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { - qCDebug(entities) << "'transfer_status' is 'pending', checking again in 90 seconds..." << entityItemID; - QTimer* transferStatusRetryTimer = new QTimer(this); - connect(transferStatusRetryTimer, &QTimer::timeout, this, [=]() { - validatePop(certID, entityItemID, senderNode, true); - }); - transferStatusRetryTimer->setSingleShot(true); - transferStatusRetryTimer->start(90000); -} - QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) { QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-" QByteArray nonceBytes = nonce.toByteArray(); @@ -1321,7 +1311,7 @@ void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, c nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge)))); } -void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { +void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { // Start owner verification. auto nodeList = DependencyManager::get(); // First, asynchronously hit "proof_of_purchase_status?transaction_type=transfer" endpoint. @@ -1352,30 +1342,13 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt withWriteLock([&] { deleteEntity(entityItemID, true); }); - } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - if (isRetryingValidation) { - qCDebug(entities) << "'transfer_status' is 'pending' after retry, deleting entity" << entityItemID; - withWriteLock([&] { - deleteEntity(entityItemID, true); - }); - } else { - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startPendingTransferStatusTimer", - Q_ARG(const QString&, certID), - Q_ARG(const EntityItemID&, entityItemID), - Q_ARG(const SharedNodePointer&, senderNode)); - return; - } else { - startPendingTransferStatusTimer(certID, entityItemID, senderNode); - } - } } else { // Second, challenge ownership of the PoP cert + // (ignore pending status; a failure will be cleaned up during DDV) sendChallengeOwnershipPacket(certID, jsonObject["transfer_recipient_key"].toString(), entityItemID, senderNode); - } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID @@ -1619,7 +1592,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c // Delete the entity we just added if it doesn't pass static certificate verification deleteEntity(entityItemID, true); } else { - validatePop(properties.getCertificateID(), entityItemID, senderNode, false); + validatePop(properties.getCertificateID(), entityItemID, senderNode); } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index a080801a0e..3289101967 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -397,12 +397,11 @@ protected: QHash _entitiesToAdd; Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID); - Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); private: void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); - void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); + void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); std::shared_ptr _myAvatar{ nullptr }; From acb21cc96a459721bf51638bba15855e36892cd2 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 25 Apr 2018 09:43:20 -0700 Subject: [PATCH 57/83] Revert "HOTFIX version of PR 12969: Attempt to shutdown web surfaces more consistently" --- interface/src/Application.cpp | 13 +--- interface/src/Application.h | 7 +-- interface/src/ui/overlays/Web3DOverlay.cpp | 12 ++-- .../src/RenderableWebEntityItem.cpp | 39 +++++------- libraries/qml/src/qml/OffscreenSurface.cpp | 6 +- .../qml/src/qml/impl/RenderEventHandler.cpp | 11 ++-- libraries/qml/src/qml/impl/SharedObject.cpp | 62 +++++++------------ .../src/AbstractViewStateInterface.h | 19 ++---- tests/render-perf/src/main.cpp | 6 +- 9 files changed, 71 insertions(+), 104 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c38caca090..789f60bdb1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4750,7 +4750,7 @@ void Application::updateLOD(float deltaTime) const { } } -void Application::pushPostUpdateLambda(void* key, const std::function& func) { +void Application::pushPostUpdateLambda(void* key, std::function func) { std::unique_lock guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } @@ -7360,7 +7360,7 @@ void Application::windowMinimizedChanged(bool minimized) { } } -void Application::postLambdaEvent(const std::function& f) { +void Application::postLambdaEvent(std::function f) { if (this->thread() == QThread::currentThread()) { f(); } else { @@ -7368,15 +7368,6 @@ void Application::postLambdaEvent(const std::function& f) { } } -void Application::sendLambdaEvent(const std::function& f) { - if (this->thread() == QThread::currentThread()) { - f(); - } else { - LambdaEvent event(f); - QCoreApplication::sendEvent(this, &event); - } -} - void Application::initPlugins(const QStringList& arguments) { QCommandLineOption display("display", "Preferred displays", "displays"); QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 74b0e5a110..769658b0d6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -136,8 +136,7 @@ public: Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted); ~Application(); - void postLambdaEvent(const std::function& f) override; - void sendLambdaEvent(const std::function& f) override; + void postLambdaEvent(std::function f) override; QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); @@ -241,7 +240,7 @@ public: qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } - bool isAboutToQuit() const { return _aboutToQuit; } + bool isAboutToQuit() const override { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display @@ -265,7 +264,7 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } - virtual void pushPostUpdateLambda(void* key, const std::function& func) override; + virtual void pushPostUpdateLambda(void* key, std::function func) override; void updateMyAvatarLookAtPosition(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index fcdac8c00c..1e0b50f091 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -65,10 +65,14 @@ const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; + AbstractViewStateInterface::instance()->postLambdaEvent([surface] { + if (AbstractViewStateInterface::instance()->isAboutToQuit()) { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; + } else { + surface->deleteLater(); + } }); }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f333e805ce..3b46c530cc 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -25,7 +25,7 @@ #include #include "EntitiesRendererLogging.h" -#include + using namespace render; using namespace render::entities; @@ -45,7 +45,6 @@ static int DEFAULT_MAX_FPS = 10; static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; -static const char* URL_PROPERTY = "url"; WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { if (urlString.isEmpty()) { @@ -53,7 +52,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& } const QUrl url(urlString); - if (url.scheme() == URL_SCHEME_HTTP || url.scheme() == URL_SCHEME_HTTPS || + if (url.scheme() == "http" || url.scheme() == "https" || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } @@ -165,8 +164,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (urlChanged) { if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { destroyWebSurface(); - // If we destroyed the surface, the URL change will be implicitly handled by the re-creation - urlChanged = false; } withWriteLock([&] { @@ -188,8 +185,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene return; } - if (urlChanged && _contentType == ContentType::HtmlContent) { - _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); + if (urlChanged) { + _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -260,14 +257,6 @@ bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } -static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - }); -}; - bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; @@ -275,9 +264,20 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { } ++_currentWebCount; + auto deleter = [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { + if (AbstractViewStateInterface::instance()->isAboutToQuit()) { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + } else { + webSurface->deleteLater(); + } + }); + }; // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) _webSurface->setMaxFps(DEFAULT_MAX_FPS); @@ -305,7 +305,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty(URL_PROPERTY, _lastSourceUrl); + item->setProperty("url", _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { @@ -330,11 +330,6 @@ void WebEntityRenderer::destroyWebSurface() { if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); - // Explicitly set the web URL to an empty string, in an effort to get a - // faster shutdown of any chromium processes interacting with audio - if (rootItem && _contentType == ContentType::HtmlContent) { - rootItem->setProperty(URL_PROPERTY, ""); - } if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 688f3fdb5f..2da1c41340 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -66,10 +66,14 @@ OffscreenSurface::OffscreenSurface() } OffscreenSurface::~OffscreenSurface() { - delete _sharedObject; + disconnect(qApp); + _sharedObject->destroy(); } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { + if (!_sharedObject) { + return false; + } hifi::qml::impl::TextureAndFence typedTextureAndFence; bool result = _sharedObject->fetchTexture(typedTextureAndFence); textureAndFence = typedTextureAndFence; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 945a469611..6b66ce9314 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThre qFatal("Unable to create new offscreen GL context"); } - _canvas.moveToThreadWithContext(targetThread); moveToThread(targetThread); + _canvas.moveToThreadWithContext(targetThread); } void RenderEventHandler::onInitalize() { @@ -160,8 +160,11 @@ void RenderEventHandler::onQuit() { } _shared->shutdownRendering(_canvas, _currentSize); - _canvas.doneCurrent(); - _canvas.moveToThreadWithContext(qApp->thread()); - moveToThread(qApp->thread()); + // Release the reference to the shared object. This will allow it to + // be destroyed (should happen on it's own thread). + _shared->deleteLater(); + + deleteLater(); + QThread::currentThread()->quit(); } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 9253c41b39..b2057117f6 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -72,35 +72,26 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } - SharedObject::~SharedObject() { - // After destroy returns, the rendering thread should be gone - destroy(); - - // _renderTimer is created with `this` as the parent, so need no explicit destruction - - // Destroy the event hand - if (_renderObject) { - delete _renderObject; - _renderObject = nullptr; - } - - if (_renderControl) { - delete _renderControl; - _renderControl = nullptr; - } - if (_quickWindow) { _quickWindow->destroy(); - delete _quickWindow; _quickWindow = nullptr; } - // _rootItem is parented to the quickWindow, so needs no explicit destruction - //if (_rootItem) { - // delete _rootItem; - // _rootItem = nullptr; - //} + if (_renderControl) { + _renderControl->deleteLater(); + _renderControl = nullptr; + } + + if (_renderThread) { + _renderThread->quit(); + _renderThread->deleteLater(); + } + + if (_rootItem) { + _rootItem->deleteLater(); + _rootItem = nullptr; + } releaseEngine(_qmlContext->engine()); } @@ -128,10 +119,6 @@ void SharedObject::create(OffscreenSurface* surface) { } void SharedObject::setRootItem(QQuickItem* rootItem) { - if (_quit) { - return; - } - _rootItem = rootItem; _rootItem->setSize(_quickWindow->size()); @@ -140,6 +127,7 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); + // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -149,43 +137,35 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { } void SharedObject::destroy() { - // `destroy` is idempotent, it can be called multiple times without issues if (_quit) { return; } if (!_rootItem) { + deleteLater(); return; } + _paused = true; if (_renderTimer) { - _renderTimer->stop(); QObject::disconnect(_renderTimer); + _renderTimer->deleteLater(); } - if (_renderControl) { - QObject::disconnect(_renderControl); - } - + QObject::disconnect(_renderControl); QObject::disconnect(qApp); { QMutexLocker lock(&_mutex); _quit = true; - if (_renderObject) { - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); - } + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, // but I haven't found a reliable way to do this only at application // shutdown - if (_renderThread) { - _renderThread->wait(); - delete _renderThread; - _renderThread = nullptr; - } + _renderThread->wait(); } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 54fdc903ca..96e9f4d222 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -37,25 +37,15 @@ public: virtual glm::vec3 getAvatarPosition() const = 0; - // Unfortunately, having this here is a bad idea. Lots of objects connect to - // the aboutToQuit signal, and it's impossible to know the order in which - // the receivers will be called, so this might return false negatives - //virtual bool isAboutToQuit() const = 0; - - // Queue code to execute on the main thread. - // If called from the main thread, the lambda will execute synchronously - virtual void postLambdaEvent(const std::function& f) = 0; - // Synchronously execute code on the main thread. This function will - // not return until the code is executed, regardles of which thread it - // is called from - virtual void sendLambdaEvent(const std::function& f) = 0; + virtual bool isAboutToQuit() const = 0; + virtual void postLambdaEvent(std::function f) = 0; virtual qreal getDevicePixelRatio() = 0; virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; - virtual void pushPostUpdateLambda(void* key, const std::function& func) = 0; + virtual void pushPostUpdateLambda(void* key, std::function func) = 0; virtual bool isHMDMode() const = 0; @@ -64,4 +54,5 @@ public: static void setInstance(AbstractViewStateInterface* instance); }; -#endif // hifi_AbstractViewStateInterface_h + +#endif // hifi_AbstractViewStateInterface_h diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 9249b3d957..93672cc5a2 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -453,8 +453,8 @@ protected: return vec3(); } - void postLambdaEvent(const std::function& f) override {} - void sendLambdaEvent(const std::function& f) override {} + bool isAboutToQuit() const override { return false; } + void postLambdaEvent(std::function f) override {} qreal getDevicePixelRatio() override { return 1.0f; @@ -469,7 +469,7 @@ protected: } std::map> _postUpdateLambdas; - void pushPostUpdateLambda(void* key, const std::function& func) override { + void pushPostUpdateLambda(void* key, std::function func) override { _postUpdateLambdas[key] = func; } From db5e5ba99beab1a45c1aa6b21794414f5eff93ce Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 27 Apr 2018 11:30:45 -0700 Subject: [PATCH 58/83] ignore right clicks --- scripts/system/controllers/controllerModules/equipEntity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 0dca6f11f4..6de62eef85 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -790,7 +790,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; var onMousePress = function(event) { - if (isInEditMode()) { // don't consider any mouse clicks on the entity while in edit + if (isInEditMode() || event.isRightButton) { // don't consider any mouse clicks on the entity while in edit return; } var pickRay = Camera.computePickRay(event.x, event.y); From 684d2b08ba6206ff1e1f49f925a7e651004a36d9 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 27 Apr 2018 11:04:46 -0700 Subject: [PATCH 59/83] Finalize work on MS14295 and MS14559 --- .../qml/hifi/commerce/common/sendAsset/SendAsset.qml | 9 +++++---- scripts/system/commerce/wallet.js | 12 +++++++----- scripts/system/marketplaces/marketplaces.js | 12 +++++++----- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 8411a24bf5..c7c72e5f7c 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -1308,7 +1308,7 @@ Item { anchors.right: parent.right; anchors.rightMargin: root.assetName === "" ? 15 : 50; anchors.bottom: parent.bottom; - anchors.bottomMargin: root.assetName === "" ? 15 : 300; + anchors.bottomMargin: root.assetName === "" ? 15 : 240; color: "#FFFFFF"; RalewaySemiBold { @@ -1403,12 +1403,12 @@ Item { id: giftContainer_paymentSuccess; visible: root.assetName !== ""; anchors.top: sendToContainer_paymentSuccess.bottom; - anchors.topMargin: 16; + anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 20; anchors.right: parent.right; anchors.rightMargin: 20; - height: 80; + height: 30; RalewaySemiBold { id: gift_paymentSuccess; @@ -1431,6 +1431,7 @@ Item { anchors.top: parent.top; anchors.left: gift_paymentSuccess.right; anchors.right: parent.right; + height: parent.height; // Text size size: 18; // Style @@ -1522,7 +1523,7 @@ Item { colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; + anchors.bottomMargin: root.assetName === "" ? 80 : 30; height: 50; width: 120; text: "Close"; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 2fbeaec317..aea752c565 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -557,12 +557,14 @@ } if (onWalletScreen) { + if (!isWired) { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } isWired = true; - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); } else { off(); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index b128e100a1..a05778e2dd 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -1055,12 +1055,14 @@ var selectionDisplay = null; // for gridTool.js to ignore } if (onCommerceScreen) { + if (!isWired) { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } isWired = true; - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); Wallet.refreshWalletStatus(); } else { off(); From f731a2d08eed6eaede593d3fe17b23a1256b7038 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 27 Apr 2018 13:32:05 -0700 Subject: [PATCH 60/83] change to not left button --- scripts/system/controllers/controllerModules/equipEntity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 6de62eef85..d6e18376bf 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -790,7 +790,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; var onMousePress = function(event) { - if (isInEditMode() || event.isRightButton) { // don't consider any mouse clicks on the entity while in edit + if (isInEditMode() || !event.isLeftButton) { // don't consider any left clicks on the entity while in edit return; } var pickRay = Camera.computePickRay(event.x, event.y); From 5f8149fd6873ca6ad1163927826283f1809a10c8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 13:57:22 -0700 Subject: [PATCH 61/83] Remove debugging pragma --- tests/qml/src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index c23a958da7..cd5a567e95 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -44,8 +44,6 @@ #include #include -#pragma optimize("", off) - namespace gl { extern void initModuleGl(); } From 575520fe3063f3d6deac5e8266b3e886ac25d88a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 15:08:10 -0700 Subject: [PATCH 62/83] Ensure we actually delete the QML scenegraph! --- libraries/qml/src/qml/impl/SharedObject.cpp | 7 +- tests/qml/qml/controls/WebEntityView.qml | 15 ++++ tests/qml/src/main.cpp | 84 ++++++++++++++------- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 7bcb216ba8..2fde057ca8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -90,14 +90,17 @@ SharedObject::~SharedObject() { _renderControl = nullptr; } + if (_rootItem) { + delete _rootItem; + _rootItem = nullptr; + } + if (_quickWindow) { _quickWindow->destroy(); delete _quickWindow; _quickWindow = nullptr; } - // _rootItem is parented to the quickWindow, so needs no explicit destruction - if (_qmlContext) { auto engine = _qmlContext->engine(); delete _qmlContext; diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml index a6c38f4abd..5bd29ef457 100644 --- a/tests/qml/qml/controls/WebEntityView.qml +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -10,6 +10,21 @@ import QtQuick 2.5 import QtWebEngine 1.5 +import Hifi 1.0 + +/* +TestItem { + Rectangle { + anchors.fill: parent + anchors.margins: 10 + color: "blue" + property string url: "" + ColorAnimation on color { + loops: Animation.Infinite; from: "blue"; to: "yellow"; duration: 1000 + } + } +} +*/ WebEngineView { id: webViewCore diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index cd5a567e95..ec4012898f 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -48,6 +48,17 @@ namespace gl { extern void initModuleGl(); } +class QTestItem : public QQuickItem { + Q_OBJECT +public: + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { + qDebug() << __FUNCTION__; + } + + ~QTestItem() { + qDebug() << __FUNCTION__; + } +}; QUrl getTestResource(const QString& relativePath) { static QString dir; @@ -89,9 +100,10 @@ private: GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; + uint64_t _createStopTime; void initGl(); void updateSurfaces(); - void buildSurface(QmlInfo& qmlInfo); + void buildSurface(QmlInfo& qmlInfo, bool allowVideo); void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); @@ -111,8 +123,10 @@ TestWindow::TestWindow() { QSurfaceFormat::setDefaultFormat(format); setFormat(format); - show(); + qmlRegisterType("Hifi", 1, 0, "TestItem"); + show(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); @@ -167,40 +181,56 @@ QString getSourceUrl() { return SOURCE_URLS[index]; } +#define CACHE_WEBVIEWS 0 +#if CACHE_WEBVIEWS +static std::list _cache; +#endif -void TestWindow::buildSurface(QmlInfo& qmlInfo) { +hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl()); +}; + +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool allowVideo) { ++_surfaceCount; - auto lifetimeSecs = (uint32_t)(2.0f + (randFloat() * 10.0f)); + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); qmlInfo.texture = 0; - auto& surface = qmlInfo.surface; - surface.reset(new hifi::qml::OffscreenSurface()); - surface->setMaxFps(DEFAULT_MAX_FPS); - surface->resize(_qmlSize); - surface->setMaxFps(DEFAULT_MAX_FPS); - hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl()); - }; - surface->load(getTestResource(CONTROL_URL), callback); - surface->resume(); +#if CACHE_WEBVIEWS + if (_cache.empty()) { + _cache.emplace_back(new hifi::qml::OffscreenSurface()); + auto& surface = _cache.back(); + surface->load(getTestResource(CONTROL_URL)); + surface->setMaxFps(DEFAULT_MAX_FPS); + } + qmlInfo.surface = _cache.front(); + _cache.pop_front(); +#else + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL)); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); +#endif + + qmlInfo.surface->resize(_qmlSize); + auto url = allowVideo ? "https://www.youtube.com/watch?v=gDXwhHm4GhM" : getSourceUrl(); + qmlInfo.surface->getRootItem()->setProperty(URL_PROPERTY, url); + qmlInfo.surface->resume(); } + void TestWindow::destroySurface(QmlInfo& qmlInfo) { auto& surface = qmlInfo.surface; - QQuickItem* rootItem = surface->getRootItem(); - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (!obj && rootItem->objectName() == "webEngineView") { - obj = rootItem; - } - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); } surface->pause(); +#if CACHE_WEBVIEWS + _cache.push_back(surface); +#endif surface.reset(); } @@ -211,8 +241,8 @@ void TestWindow::updateSurfaces() { for (size_t y = 0; y < DIVISIONS_Y; ++y) { auto& qmlInfo = _surfaces[x][y]; if (!qmlInfo.surface) { - if (randFloat() > 0.99f) { - buildSurface(qmlInfo); + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); } else { continue; } @@ -298,3 +328,5 @@ int main(int argc, char** argv) { app.exec(); return 0; } + +#include "main.moc" \ No newline at end of file From 0962e1fc1658f25f44ded0128d93ed802305ee25 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 27 Apr 2018 11:30:45 -0700 Subject: [PATCH 63/83] ignore right clicks --- scripts/system/controllers/controllerModules/equipEntity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 53dbee829d..1588a17538 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -796,7 +796,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; var onMousePress = function(event) { - if (isInEditMode()) { // don't consider any mouse clicks on the entity while in edit + if (isInEditMode() || event.isRightButton) { // don't consider any mouse clicks on the entity while in edit return; } var pickRay = Camera.computePickRay(event.x, event.y); From 61afd12eeb91539a7fa50362aeda9276ac3fd718 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 27 Apr 2018 13:32:05 -0700 Subject: [PATCH 64/83] change to not left button --- scripts/system/controllers/controllerModules/equipEntity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 1588a17538..1fce772ec8 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -796,7 +796,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; var onMousePress = function(event) { - if (isInEditMode() || event.isRightButton) { // don't consider any mouse clicks on the entity while in edit + if (isInEditMode() || !event.isLeftButton) { // don't consider any left clicks on the entity while in edit return; } var pickRay = Camera.computePickRay(event.x, event.y); From 8e42bb8c877e693cdf29bdcb2e206316a2bc8a85 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 16:04:07 -0700 Subject: [PATCH 65/83] Restore the stop functionality for a browser view when it's being destroyed --- .../qml/controls/FlickableWebViewCore.qml | 5 ++ interface/resources/qml/controls/WebView.qml | 4 ++ .../src/RenderableWebEntityItem.cpp | 11 ++- tests/qml/src/main.cpp | 68 +++++-------------- 4 files changed, 32 insertions(+), 56 deletions(-) diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 8e7db44b7d..943f15e1de 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -21,6 +21,7 @@ Item { signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) + width: parent.width property bool interactive: false @@ -29,6 +30,10 @@ Item { id: hifi } + function stop() { + webViewCore.stop(); + } + function unfocus() { webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { console.log('unfocus completed: ', result); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 931c64e1ef..71bf69fdc8 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -21,6 +21,10 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive + function stop() { + webroot.stop(); + } + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. /* diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 7ad74d1eee..693e3d0cf4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -318,8 +318,10 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; + ContentType contentType{ ContentType::NoContent }; withWriteLock([&] { webSurface.swap(_webSurface); + std::swap(contentType, _contentType); }); if (webSurface) { @@ -328,12 +330,9 @@ void WebEntityRenderer::destroyWebSurface() { // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + if (rootItem && contentType == ContentType::HtmlContent) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); } webSurface->pause(); diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index ec4012898f..349ac55d88 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -45,19 +45,15 @@ #include namespace gl { - extern void initModuleGl(); +extern void initModuleGl(); } class QTestItem : public QQuickItem { Q_OBJECT public: - QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { - qDebug() << __FUNCTION__; - } + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { qDebug() << __FUNCTION__; } - ~QTestItem() { - qDebug() << __FUNCTION__; - } + ~QTestItem() { qDebug() << __FUNCTION__; } }; QUrl getTestResource(const QString& relativePath) { @@ -71,7 +67,6 @@ QUrl getTestResource(const QString& relativePath) { return QUrl::fromLocalFile(dir + relativePath); } - #define DIVISIONS_X 5 #define DIVISIONS_Y 5 @@ -164,61 +159,41 @@ void TestWindow::resizeWindow(const QSize& size) { } static const int DEFAULT_MAX_FPS = 10; -static const int YOUTUBE_MAX_FPS = 30; static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; static const char* URL_PROPERTY{ "url" }; -QString getSourceUrl() { +QString getSourceUrl(bool video) { static const std::vector SOURCE_URLS{ "https://www.reddit.com/wiki/random", "https://en.wikipedia.org/wiki/Wikipedia:Random", "https://slashdot.org/", - //"https://www.youtube.com/watch?v=gDXwhHm4GhM", - //"https://www.youtube.com/watch?v=Ch_hoYPPeGc", }; - auto index = rand() % SOURCE_URLS.size(); - return SOURCE_URLS[index]; + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; } -#define CACHE_WEBVIEWS 0 - -#if CACHE_WEBVIEWS -static std::list _cache; -#endif - -hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl()); -}; - -void TestWindow::buildSurface(QmlInfo& qmlInfo, bool allowVideo) { +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { ++_surfaceCount; auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); qmlInfo.texture = 0; -#if CACHE_WEBVIEWS - if (_cache.empty()) { - _cache.emplace_back(new hifi::qml::OffscreenSurface()); - auto& surface = _cache.back(); - surface->load(getTestResource(CONTROL_URL)); - surface->setMaxFps(DEFAULT_MAX_FPS); - } - qmlInfo.surface = _cache.front(); - _cache.pop_front(); -#else qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); - qmlInfo.surface->load(getTestResource(CONTROL_URL)); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); -#endif - qmlInfo.surface->resize(_qmlSize); - auto url = allowVideo ? "https://www.youtube.com/watch?v=gDXwhHm4GhM" : getSourceUrl(); - qmlInfo.surface->getRootItem()->setProperty(URL_PROPERTY, url); qmlInfo.surface->resume(); } - void TestWindow::destroySurface(QmlInfo& qmlInfo) { auto& surface = qmlInfo.surface; auto webView = surface->getRootItem(); @@ -228,9 +203,6 @@ void TestWindow::destroySurface(QmlInfo& qmlInfo) { webView->setProperty(URL_PROPERTY, "about:blank"); } surface->pause(); -#if CACHE_WEBVIEWS - _cache.push_back(surface); -#endif surface.reset(); } @@ -296,7 +268,6 @@ void TestWindow::draw() { _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - for (uint32_t x = 0; x < DIVISIONS_X; ++x) { for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { auto& qmlInfo = _surfaces[x][y]; @@ -313,8 +284,6 @@ void TestWindow::draw() { GL_COLOR_BUFFER_BIT, GL_NEAREST); } } - _glf.glFlush(); - _glContext.swapBuffers(this); } @@ -325,8 +294,7 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { int main(int argc, char** argv) { QGuiApplication app(argc, argv); TestWindow window; - app.exec(); - return 0; + return app.exec(); } -#include "main.moc" \ No newline at end of file +#include "main.moc" From a226790295c8ef4c5e21179993bb5cc0d62995e2 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Mon, 30 Apr 2018 15:05:27 +0300 Subject: [PATCH 66/83] Eliminated warnings in gcc Note that the call to encodeNode() in FBXWriter.cpp can never be reached, so it's safe to remove it. --- assignment-client/src/assets/AssetServer.h | 2 +- libraries/audio/src/InboundAudioStream.cpp | 3 +++ libraries/entities/src/EntityTree.cpp | 3 +++ libraries/fbx/src/FBXWriter.cpp | 1 - libraries/networking/src/ResourceCache.cpp | 6 ++++++ libraries/shared/src/Gzip.cpp | 3 +++ 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index fb88df0171..f83545c25c 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -63,7 +63,7 @@ struct AssetMeta { AssetMeta() { } - BakeVersion bakeVersion; + BakeVersion bakeVersion { INITIAL_BAKE_VERSION }; bool failedLastBake { false }; QString lastBakeErrors; }; diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 172ec9411a..bbaedc003b 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -149,6 +149,9 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { lostAudioData(packetsDropped); // fall through to OnTime case +#if defined(__GNUC__) + [[gnu::fallthrough]]; +#endif } case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b56f367e0a..e9e4f2631a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1402,6 +1402,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c case PacketType::EntityAdd: isAdd = true; // fall through to next case +#if defined(__GNUC__) + [[gnu::fallthrough]]; +#endif case PacketType::EntityPhysics: case PacketType::EntityEdit: { quint64 startDecode = 0, endDecode = 0; diff --git a/libraries/fbx/src/FBXWriter.cpp b/libraries/fbx/src/FBXWriter.cpp index 511f253193..e6adff0df9 100644 --- a/libraries/fbx/src/FBXWriter.cpp +++ b/libraries/fbx/src/FBXWriter.cpp @@ -142,7 +142,6 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { out << prop.toInt(); break; - encodeNode(out, FBXNode()); case QMetaType::Float: out.device()->write("F", 1); out << prop.toFloat(); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7ba7cca96d..3b8c9fdac0 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -749,6 +749,9 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { case ResourceRequest::Result::Timeout: { qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; // Fall through to other cases +#if defined(__GNUC__) + [[gnu::fallthrough]]; +#endif } case ResourceRequest::Result::ServerUnavailable: { _attempts++; @@ -767,6 +770,9 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { break; } // fall through to final failure +#if defined(__GNUC__) + [[gnu::fallthrough]]; +#endif } default: { _attemptsRemaining = 0; diff --git a/libraries/shared/src/Gzip.cpp b/libraries/shared/src/Gzip.cpp index a77f459fc9..44fe13f439 100644 --- a/libraries/shared/src/Gzip.cpp +++ b/libraries/shared/src/Gzip.cpp @@ -60,6 +60,9 @@ bool gunzip(QByteArray source, QByteArray &destination) { switch (status) { case Z_NEED_DICT: status = Z_DATA_ERROR; +#if defined(__GNUC__) + [[gnu::fallthrough]]; +#endif case Z_DATA_ERROR: case Z_MEM_ERROR: case Z_STREAM_ERROR: From 2de982a5a2751d27989cef07630d94cb62daa2c1 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 30 Apr 2018 12:04:35 -0700 Subject: [PATCH 67/83] Added script types and better script reload --- interface/src/Application.cpp | 23 +++++++++++-------- interface/src/Application.h | 2 +- interface/src/ModelPackager.cpp | 1 + interface/src/avatar/MyAvatar.cpp | 6 +---- interface/src/avatar/MyAvatar.h | 5 ---- libraries/fbx/src/FSTReader.cpp | 22 ++++++++++++++++++ libraries/fbx/src/FSTReader.h | 2 ++ .../src/model-networking/ModelCache.cpp | 19 +++++++-------- libraries/script-engine/src/ScriptEngine.cpp | 14 +++++++++++ libraries/script-engine/src/ScriptEngine.h | 12 ++++++++++ libraries/script-engine/src/ScriptEngines.cpp | 6 +++-- libraries/script-engine/src/ScriptEngines.h | 2 +- 12 files changed, 81 insertions(+), 33 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c38bb9295a..833224510b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1231,6 +1231,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { getEntities()->reloadEntityScripts(); + loadAvatarScripts(getMyAvatar()->getSkeletonModel()->getFBXGeometry().scripts); }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptLoadError, @@ -2285,10 +2286,6 @@ void Application::onAboutToQuit() { // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get()->hide("RunningScripts"); - if (auto avatar = getMyAvatar()) { - auto urls = avatar->getScriptsToUnload(); - unloadAvatarScripts(urls); - } _aboutToQuit = true; @@ -4737,18 +4734,24 @@ void Application::loadAvatarScripts(const QVector& urls) { for (auto url : urls) { int index = runningScripts.indexOf(url); if (index < 0) { - scriptEngines->loadScript(url); - getMyAvatar()->addScriptToUnload(url); + auto scriptEnginePointer = scriptEngines->loadScript(url, false); + if (scriptEnginePointer) { + scriptEnginePointer->setType(ScriptEngine::Type::AVATAR); + } } } } } -void Application::unloadAvatarScripts(const QVector& urls) { - if (urls.size() > 0) { - auto scriptEngines = DependencyManager::get(); +void Application::unloadAvatarScripts() { + auto scriptEngines = DependencyManager::get(); + auto urls = scriptEngines->getRunningScripts(); + if (urls.size() > 0) { for (auto url : urls) { - scriptEngines->stopScript(url, false); + auto scriptEngine = scriptEngines->getScriptEngine(url); + if (scriptEngine->getType() == ScriptEngine::Type::AVATAR) { + scriptEngines->stopScript(url, false); + } } } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 2e91f842a4..28c85ec855 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -291,7 +291,7 @@ public: void replaceDomainContent(const QString& url); void loadAvatarScripts(const QVector& urls); - void unloadAvatarScripts(const QVector& urls); + void unloadAvatarScripts(); signals: void svoImportRequested(const QString& url); diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index a87669bf47..72b99ce5a3 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -177,6 +177,7 @@ bool ModelPackager::zipModel() { _scriptDir = _modelFile.path() + "/" + scriptField; QDir wdir = QDir(_scriptDir); _mapping.remove(SCRIPT_FIELD); + wdir.setSorting(QDir::Name | QDir::Reversed); auto list = wdir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries); for (auto script : list) { auto sc = tempDir.relativeFilePath(scriptDir.path()) + "/" + QUrl(script).fileName(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 462dbebbbb..99fdea0449 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -123,6 +123,7 @@ MyAvatar::MyAvatar(QThread* thread) : connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); connect(_skeletonModel.get(), &Model::setURLFinished, this, [this](bool success) { if (success) { + qApp->unloadAvatarScripts(); auto geometry = getSkeletonModel()->getFBXGeometry(); qApp->loadAvatarScripts(geometry.scripts); } @@ -1469,7 +1470,6 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { - qApp->unloadAvatarScripts(_scriptsToUnload); _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); @@ -2838,10 +2838,6 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } -void MyAvatar::addScriptToUnload(const QString& url) { - _scriptsToUnload.push_back(url); -} - void MyAvatar::setSprintMode(bool sprint) { _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 00cb50e079..a927a1d0ba 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -594,9 +594,6 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; - void addScriptToUnload(const QString& url); - const QVector& getScriptsToUnload() const { return _scriptsToUnload; }; - public slots: void increaseSize(); void decreaseSize(); @@ -907,8 +904,6 @@ private: // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; - - QVector _scriptsToUnload; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 603f214c3e..fcbfca1e7d 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -187,6 +187,28 @@ FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) { return ENTITY_MODEL; } +QVector FSTReader::getScripts(const QUrl& url, const QVariantHash& mapping) { + + auto fstMapping = mapping.isEmpty() ? downloadMapping(url.toString()) : mapping; + QVector scriptPaths; + if (!fstMapping.value(SCRIPT_FIELD).isNull()) { + auto scripts = fstMapping.values(SCRIPT_FIELD).toVector(); + if (scripts.size() > 0) { + for (auto &script : scripts) { + QString scriptPath = script.toString(); + if (QUrl(scriptPath).isRelative()) { + if (scriptPath.at(0) == '/') { + scriptPath = scriptPath.right(scriptPath.length() - 1); + } + scriptPath = url.resolved(QUrl(scriptPath)).toString(); + } + scriptPaths.push_back(scriptPath); + } + } + } + return scriptPaths; +} + QVariantHash FSTReader::downloadMapping(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index d1204c0876..4a8574f0cf 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -50,6 +50,8 @@ public: /// Predicts the type of model by examining the mapping static ModelType predictModelType(const QVariantHash& mapping); + static QVector getScripts(const QUrl& fstUrl, const QVariantHash& mapping = QVariantHash()); + static QString getNameFromType(ModelType modelType); static FSTReader::ModelType getTypeFromName(const QString& name); static QVariantHash downloadMapping(const QString& url); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index e3f543c403..e1086bc0c9 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -83,6 +83,14 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { _textureBaseUrl = url.resolved(QUrl(".")); } + auto scripts = FSTReader::getScripts(_url, mapping); + if (scripts.size() > 0) { + mapping.remove(SCRIPT_FIELD); + for (auto &scriptPath : scripts) { + mapping.insertMulti(SCRIPT_FIELD, scriptPath); + } + } + auto animGraphVariant = mapping.value("animGraphUrl"); if (animGraphVariant.isValid()) { QUrl fstUrl(animGraphVariant.toString()); @@ -210,19 +218,12 @@ void GeometryReader::run() { throw QString("unsupported format"); } - // Store fst scripts on geometry + // Add scripts to fbxgeometry if (!_mapping.value(SCRIPT_FIELD).isNull()) { QVariantList scripts = _mapping.values(SCRIPT_FIELD); if (scripts.size() > 0) { for (auto &script : scripts) { - QString scriptUrl = script.toString(); - if (QUrl(scriptUrl).isRelative()) { - if (scriptUrl.at(0) == '/') { - scriptUrl = scriptUrl.right(scriptUrl.length() - 1); - } - scriptUrl = _url.resolved(QUrl(scriptUrl)).toString(); - } - fbxGeometry->scripts.push_back(scriptUrl); + fbxGeometry->scripts.push_back(script.toString()); } } } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c79ffffec7..d4912fbd3d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -180,6 +180,20 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const // don't delete `ScriptEngines` until all `ScriptEngine`s are gone _scriptEngines(DependencyManager::get()) { + switch (_context) { + case Context::CLIENT_SCRIPT: + _type = Type::CLIENT; + break; + case Context::ENTITY_CLIENT_SCRIPT: + _type = Type::ENTITY_CLIENT; + break; + case Context::ENTITY_SERVER_SCRIPT: + _type = Type::ENTITY_SERVER; + break; + case Context::AGENT_SCRIPT: + _type = Type::AGENT; + } + connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { if (hasUncaughtException()) { // the engine's uncaughtException() seems to produce much better stack traces here diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 7f69eee990..cf2606330f 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -99,6 +99,14 @@ public: AGENT_SCRIPT }; + enum Type { + CLIENT, + ENTITY_CLIENT, + ENTITY_SERVER, + AGENT, + AVATAR + }; + static int processLevelMaxRetries; ScriptEngine(Context context, const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("about:ScriptEngine")); ~ScriptEngine(); @@ -209,6 +217,9 @@ public: Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } + void setType(Type type) { _type = type; }; + Type getType() { return _type; }; + bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget @@ -293,6 +304,7 @@ protected: void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); Context _context; + Type _type; QString _scriptContents; QString _parentURL; std::atomic _isFinished { false }; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 871705d74b..59d431fabb 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -427,11 +427,13 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { if (_scriptEnginesHash.contains(scriptURL)) { ScriptEnginePointer scriptEngine = _scriptEnginesHash[scriptURL]; if (restart) { + bool isUserLoaded = scriptEngine->isUserLoaded(); + ScriptEngine::Type type = scriptEngine->getType(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); connect(scriptEngine.data(), &ScriptEngine::finished, - this, [this](QString scriptName, ScriptEnginePointer engine) { - reloadScript(scriptName); + this, [this, isUserLoaded, type](QString scriptName, ScriptEnginePointer engine) { + reloadScript(scriptName, isUserLoaded)->setType(type); }); } scriptEngine->stop(); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index ea07ebe840..63b18c73ea 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -110,7 +110,7 @@ protected slots: protected: friend class ScriptEngine; - void reloadScript(const QString& scriptName) { loadScript(scriptName, true, false, false, true); } + ScriptEnginePointer reloadScript(const QString& scriptName, bool isUserLoaded = true) { return loadScript(scriptName, isUserLoaded, false, false, true); } void removeScriptEngine(ScriptEnginePointer); void onScriptEngineLoaded(const QString& scriptFilename); void onScriptEngineError(const QString& scriptFilename); From ed47cd84581d062dc5127a9f09b6a8d655a1f93b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 30 Apr 2018 12:35:48 -0700 Subject: [PATCH 68/83] add a missing unsafe_erase for local node ID map --- libraries/networking/src/LimitedNodeList.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 31500be682..2bdd688a73 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -302,6 +302,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } } else { NLPacket::LocalID sourceLocalID = Node::NULL_LOCAL_ID; + // check if we were passed a sourceNode hint or if we need to look it up if (!sourceNode) { // figure out which node this is from @@ -608,6 +609,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newCo { QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(matchingNode->getLocalID()); _nodeHash.unsafe_erase(it); } From 7db07db9e2bdfd4bb92544a951e8e94b38f6ac97 Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Mon, 30 Apr 2018 13:55:01 -0700 Subject: [PATCH 69/83] remove menu item at cleanup --- scripts/system/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6d2b1f129b..c99c8d401a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1291,6 +1291,7 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); } Script.scriptEnding.connect(function () { From 50ca09b3b421d44fa9a27177f8b7291b94a1572a Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 30 Apr 2018 16:26:04 -0700 Subject: [PATCH 70/83] minor fixes --- interface/src/Application.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 833224510b..ec40ffc653 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2286,7 +2286,7 @@ void Application::onAboutToQuit() { // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get()->hide("RunningScripts"); - + _aboutToQuit = true; cleanupBeforeQuit(); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d4912fbd3d..255a4832a9 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -192,6 +192,7 @@ ScriptEngine::ScriptEngine(Context context, const QString& scriptContents, const break; case Context::AGENT_SCRIPT: _type = Type::AGENT; + break; } connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { From f9bda6df15f125b2352dc19a1f6a70b38cea10a2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 1 May 2018 11:36:37 +1200 Subject: [PATCH 71/83] Fix LODManager JSDoc --- interface/src/LODManager.h | 49 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index e8737d92ae..96d92e91e9 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -9,11 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/**jsdoc - * The LOD class manages your Level of Detail functions within interface - * @namespace LODManager - */ - #ifndef hifi_LODManager_h #define hifi_LODManager_h @@ -39,10 +34,32 @@ const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; class AABox; +/**jsdoc + * The LOD class manages your Level of Detail functions within Interface. + * @namespace LODManager + * @property {number} presentTime Read-only. + * @property {number} engineRunTime Read-only. + * @property {number} gpuTime Read-only. + * @property {number} avgRenderTime Read-only. + * @property {number} fps Read-only. + * @property {number} lodLevel Read-only. + * @property {number} lodDecreaseFPS Read-only. + * @property {number} lodIncreaseFPS Read-only. + */ + class LODManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + Q_PROPERTY(float presentTime READ getPresentTime) + Q_PROPERTY(float engineRunTime READ getEngineRunTime) + Q_PROPERTY(float gpuTime READ getGPUTime) + Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) + Q_PROPERTY(float fps READ getMaxTheoreticalFPS) + Q_PROPERTY(float lodLevel READ getLODLevel) + Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) + Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) + public: /**jsdoc @@ -138,28 +155,6 @@ public: */ Q_INVOKABLE float getLODIncreaseFPS() const; - /**jsdoc - * @namespace LODManager - * @property {number} presentTime Read-only. - * @property {number} engineRunTime Read-only. - * @property {number} gpuTime Read-only. - * @property {number} avgRenderTime Read-only. - * @property {number} fps Read-only. - * @property {number} lodLevel Read-only. - * @property {number} lodDecreaseFPS Read-only. - * @property {number} lodIncreaseFPS Read-only. - */ - - Q_PROPERTY(float presentTime READ getPresentTime) - Q_PROPERTY(float engineRunTime READ getEngineRunTime) - Q_PROPERTY(float gpuTime READ getGPUTime) - Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) - Q_PROPERTY(float fps READ getMaxTheoreticalFPS) - Q_PROPERTY(float lodLevel READ getLODLevel) - - Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) - Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) - float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } float getGPUTime() const { return _gpuTime; } From f69ee410748700e547c7fffb5e5a5ee2021b20f6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 30 Apr 2018 17:13:44 -0700 Subject: [PATCH 72/83] Prevent crash in ImageProvider when tablet isn't yet initialized --- interface/src/commerce/Wallet.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 3e0e4adf18..42c8b9973d 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -615,9 +615,12 @@ void Wallet::updateImageProvider() { securityImageProvider->setSecurityImage(_securityImage); // inform tablet security image provider - QQmlEngine* tabletEngine = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")->getTabletSurface()->getSurfaceContext()->engine(); - securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); - securityImageProvider->setSecurityImage(_securityImage); + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + if (tablet) { + QQmlEngine* tabletEngine = tablet->getTabletSurface()->getSurfaceContext()->engine(); + securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); + securityImageProvider->setSecurityImage(_securityImage); + } } void Wallet::chooseSecurityImage(const QString& filename) { From 2ac7fcadd2c02f89b0655631d1ee3b9c4660bad5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 1 May 2018 09:37:01 -0700 Subject: [PATCH 73/83] load scripts once on rigReady --- interface/src/avatar/MyAvatar.cpp | 6 ++++++ interface/src/avatar/MyAvatar.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 99fdea0449..85b2ece077 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -124,8 +124,14 @@ MyAvatar::MyAvatar(QThread* thread) : connect(_skeletonModel.get(), &Model::setURLFinished, this, [this](bool success) { if (success) { qApp->unloadAvatarScripts(); + _shouldLoadScripts = true; + } + }); + connect(_skeletonModel.get(), &Model::rigReady, this, [this]() { + if (_shouldLoadScripts) { auto geometry = getSkeletonModel()->getFBXGeometry(); qApp->loadAvatarScripts(geometry.scripts); + _shouldLoadScripts = false; } }); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a927a1d0ba..2bcbd878a9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -904,6 +904,9 @@ private: // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; + + // load avatar scripts once when rig is ready + bool _shouldLoadScripts { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); From cd76336e6a937128a5b8f227ae57a3e856431914 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 1 May 2018 09:54:23 -0700 Subject: [PATCH 74/83] Actually fix --- interface/src/commerce/Wallet.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 42c8b9973d..35e6ca1c92 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -615,11 +615,14 @@ void Wallet::updateImageProvider() { securityImageProvider->setSecurityImage(_securityImage); // inform tablet security image provider - auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); if (tablet) { - QQmlEngine* tabletEngine = tablet->getTabletSurface()->getSurfaceContext()->engine(); - securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); - securityImageProvider->setSecurityImage(_securityImage); + OffscreenQmlSurface* tabletSurface = tablet->getTabletSurface(); + if (tabletSurface) { + QQmlEngine* tabletEngine = tabletSurface->getSurfaceContext()->engine(); + securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); + securityImageProvider->setSecurityImage(_securityImage); + } } } From cad63ad1071e9a3eb7f742ac7543d9107c089682 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 1 May 2018 14:15:12 -0700 Subject: [PATCH 75/83] Revert "Revert "HOTFIX version of PR 12969: Attempt to shutdown web surfaces more consistently"" This reverts commit acb21cc96a459721bf51638bba15855e36892cd2. --- interface/src/Application.cpp | 13 ++++- interface/src/Application.h | 7 ++- interface/src/ui/overlays/Web3DOverlay.cpp | 12 ++-- .../src/RenderableWebEntityItem.cpp | 39 +++++++------ libraries/qml/src/qml/OffscreenSurface.cpp | 6 +- .../qml/src/qml/impl/RenderEventHandler.cpp | 11 ++-- libraries/qml/src/qml/impl/SharedObject.cpp | 56 +++++++++++++------ .../src/AbstractViewStateInterface.h | 19 +++++-- tests/render-perf/src/main.cpp | 6 +- 9 files changed, 101 insertions(+), 68 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 789f60bdb1..c38caca090 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4750,7 +4750,7 @@ void Application::updateLOD(float deltaTime) const { } } -void Application::pushPostUpdateLambda(void* key, std::function func) { +void Application::pushPostUpdateLambda(void* key, const std::function& func) { std::unique_lock guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } @@ -7360,7 +7360,7 @@ void Application::windowMinimizedChanged(bool minimized) { } } -void Application::postLambdaEvent(std::function f) { +void Application::postLambdaEvent(const std::function& f) { if (this->thread() == QThread::currentThread()) { f(); } else { @@ -7368,6 +7368,15 @@ void Application::postLambdaEvent(std::function f) { } } +void Application::sendLambdaEvent(const std::function& f) { + if (this->thread() == QThread::currentThread()) { + f(); + } else { + LambdaEvent event(f); + QCoreApplication::sendEvent(this, &event); + } +} + void Application::initPlugins(const QStringList& arguments) { QCommandLineOption display("display", "Preferred displays", "displays"); QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 769658b0d6..74b0e5a110 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -136,7 +136,8 @@ public: Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted); ~Application(); - void postLambdaEvent(std::function f) override; + void postLambdaEvent(const std::function& f) override; + void sendLambdaEvent(const std::function& f) override; QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); @@ -240,7 +241,7 @@ public: qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } - bool isAboutToQuit() const override { return _aboutToQuit; } + bool isAboutToQuit() const { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display @@ -264,7 +265,7 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } - virtual void pushPostUpdateLambda(void* key, std::function func) override; + virtual void pushPostUpdateLambda(void* key, const std::function& func) override; void updateMyAvatarLookAtPosition(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 1e0b50f091..fcdac8c00c 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -65,14 +65,10 @@ const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->postLambdaEvent([surface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; - } else { - surface->deleteLater(); - } + AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; }); }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 3b46c530cc..f333e805ce 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -25,7 +25,7 @@ #include #include "EntitiesRendererLogging.h" - +#include using namespace render; using namespace render::entities; @@ -45,6 +45,7 @@ static int DEFAULT_MAX_FPS = 10; static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; +static const char* URL_PROPERTY = "url"; WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { if (urlString.isEmpty()) { @@ -52,7 +53,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& } const QUrl url(urlString); - if (url.scheme() == "http" || url.scheme() == "https" || + if (url.scheme() == URL_SCHEME_HTTP || url.scheme() == URL_SCHEME_HTTPS || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } @@ -164,6 +165,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (urlChanged) { if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { destroyWebSurface(); + // If we destroyed the surface, the URL change will be implicitly handled by the re-creation + urlChanged = false; } withWriteLock([&] { @@ -185,8 +188,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene return; } - if (urlChanged) { - _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); + if (urlChanged && _contentType == ContentType::HtmlContent) { + _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -257,6 +260,14 @@ bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } +static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); +}; + bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; @@ -264,20 +275,9 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { } ++_currentWebCount; - auto deleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - } else { - webSurface->deleteLater(); - } - }); - }; // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) _webSurface->setMaxFps(DEFAULT_MAX_FPS); @@ -305,7 +305,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); + item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { @@ -330,6 +330,11 @@ void WebEntityRenderer::destroyWebSurface() { if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); + // Explicitly set the web URL to an empty string, in an effort to get a + // faster shutdown of any chromium processes interacting with audio + if (rootItem && _contentType == ContentType::HtmlContent) { + rootItem->setProperty(URL_PROPERTY, ""); + } if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 2da1c41340..688f3fdb5f 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -66,14 +66,10 @@ OffscreenSurface::OffscreenSurface() } OffscreenSurface::~OffscreenSurface() { - disconnect(qApp); - _sharedObject->destroy(); + delete _sharedObject; } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { - if (!_sharedObject) { - return false; - } hifi::qml::impl::TextureAndFence typedTextureAndFence; bool result = _sharedObject->fetchTexture(typedTextureAndFence); textureAndFence = typedTextureAndFence; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 6b66ce9314..945a469611 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThre qFatal("Unable to create new offscreen GL context"); } - moveToThread(targetThread); _canvas.moveToThreadWithContext(targetThread); + moveToThread(targetThread); } void RenderEventHandler::onInitalize() { @@ -160,11 +160,8 @@ void RenderEventHandler::onQuit() { } _shared->shutdownRendering(_canvas, _currentSize); - // Release the reference to the shared object. This will allow it to - // be destroyed (should happen on it's own thread). - _shared->deleteLater(); - - deleteLater(); - + _canvas.doneCurrent(); + _canvas.moveToThreadWithContext(qApp->thread()); + moveToThread(qApp->thread()); QThread::currentThread()->quit(); } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index b2057117f6..9253c41b39 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -72,26 +72,35 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } + SharedObject::~SharedObject() { - if (_quickWindow) { - _quickWindow->destroy(); - _quickWindow = nullptr; + // After destroy returns, the rendering thread should be gone + destroy(); + + // _renderTimer is created with `this` as the parent, so need no explicit destruction + + // Destroy the event hand + if (_renderObject) { + delete _renderObject; + _renderObject = nullptr; } if (_renderControl) { - _renderControl->deleteLater(); + delete _renderControl; _renderControl = nullptr; } - if (_renderThread) { - _renderThread->quit(); - _renderThread->deleteLater(); + if (_quickWindow) { + _quickWindow->destroy(); + delete _quickWindow; + _quickWindow = nullptr; } - if (_rootItem) { - _rootItem->deleteLater(); - _rootItem = nullptr; - } + // _rootItem is parented to the quickWindow, so needs no explicit destruction + //if (_rootItem) { + // delete _rootItem; + // _rootItem = nullptr; + //} releaseEngine(_qmlContext->engine()); } @@ -119,6 +128,10 @@ void SharedObject::create(OffscreenSurface* surface) { } void SharedObject::setRootItem(QQuickItem* rootItem) { + if (_quit) { + return; + } + _rootItem = rootItem; _rootItem->setSize(_quickWindow->size()); @@ -127,7 +140,6 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); - // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -137,35 +149,43 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { } void SharedObject::destroy() { + // `destroy` is idempotent, it can be called multiple times without issues if (_quit) { return; } if (!_rootItem) { - deleteLater(); return; } - _paused = true; if (_renderTimer) { + _renderTimer->stop(); QObject::disconnect(_renderTimer); - _renderTimer->deleteLater(); } - QObject::disconnect(_renderControl); + if (_renderControl) { + QObject::disconnect(_renderControl); + } + QObject::disconnect(qApp); { QMutexLocker lock(&_mutex); _quit = true; - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + if (_renderObject) { + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + } } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, // but I haven't found a reliable way to do this only at application // shutdown - _renderThread->wait(); + if (_renderThread) { + _renderThread->wait(); + delete _renderThread; + _renderThread = nullptr; + } } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 96e9f4d222..54fdc903ca 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -37,15 +37,25 @@ public: virtual glm::vec3 getAvatarPosition() const = 0; - virtual bool isAboutToQuit() const = 0; - virtual void postLambdaEvent(std::function f) = 0; + // Unfortunately, having this here is a bad idea. Lots of objects connect to + // the aboutToQuit signal, and it's impossible to know the order in which + // the receivers will be called, so this might return false negatives + //virtual bool isAboutToQuit() const = 0; + + // Queue code to execute on the main thread. + // If called from the main thread, the lambda will execute synchronously + virtual void postLambdaEvent(const std::function& f) = 0; + // Synchronously execute code on the main thread. This function will + // not return until the code is executed, regardles of which thread it + // is called from + virtual void sendLambdaEvent(const std::function& f) = 0; virtual qreal getDevicePixelRatio() = 0; virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; - virtual void pushPostUpdateLambda(void* key, std::function func) = 0; + virtual void pushPostUpdateLambda(void* key, const std::function& func) = 0; virtual bool isHMDMode() const = 0; @@ -54,5 +64,4 @@ public: static void setInstance(AbstractViewStateInterface* instance); }; - -#endif // hifi_AbstractViewStateInterface_h +#endif // hifi_AbstractViewStateInterface_h diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 93672cc5a2..9249b3d957 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -453,8 +453,8 @@ protected: return vec3(); } - bool isAboutToQuit() const override { return false; } - void postLambdaEvent(std::function f) override {} + void postLambdaEvent(const std::function& f) override {} + void sendLambdaEvent(const std::function& f) override {} qreal getDevicePixelRatio() override { return 1.0f; @@ -469,7 +469,7 @@ protected: } std::map> _postUpdateLambdas; - void pushPostUpdateLambda(void* key, std::function func) override { + void pushPostUpdateLambda(void* key, const std::function& func) override { _postUpdateLambdas[key] = func; } From 0627099667378534ca182f3874e4a2e6df70fe79 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 1 May 2018 14:33:48 -0700 Subject: [PATCH 76/83] In packetSourceAndHashMatchAndTrackBandwidth() check LocalID after check for DS source Makes check more robust in case domain server itself reaches this point, since LimitedNodeList::getDomainLocalID() debug-asserts. --- libraries/networking/src/LimitedNodeList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 31500be682..af26a2ea72 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -314,9 +314,9 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && - sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && - PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { + PacketTypeEnum::getDomainSourcedPackets().contains(headerType) && + sourceLocalID == getDomainLocalID()) { // This is a packet sourced by the domain server emit dataReceived(NodeType::Unassigned, packet.getPayloadSize()); From bbcb07d9ab7aaea8b88bd1e79c00d2604bdfd91c Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 1 May 2018 15:07:44 -0700 Subject: [PATCH 77/83] Brute force it with isDomainServer() --- libraries/networking/src/LimitedNodeList.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index af26a2ea72..e897fd13e0 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -314,9 +314,10 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && + !isDomainServer() && + sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && - PacketTypeEnum::getDomainSourcedPackets().contains(headerType) && - sourceLocalID == getDomainLocalID()) { + PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { // This is a packet sourced by the domain server emit dataReceived(NodeType::Unassigned, packet.getPayloadSize()); From 1b99946c5f691bfa9290d5547b9a21402953841d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 2 May 2018 12:38:29 +1200 Subject: [PATCH 78/83] Fix entity not highlighted in list after creating or undoing delete --- scripts/system/libraries/entityList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index d53766ab4e..611bd4d84c 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -103,7 +103,7 @@ EntityListTool = function(opts) { var selectedIDs = []; for (var j = 0; j < selectionManager.selections.length; j++) { - selectedIDs.push(selectionManager.selections[j].id); + selectedIDs.push(selectionManager.selections[j]); } var data = { From 26ffb3213cce98f585c979268afc381527395173 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Wed, 2 May 2018 09:01:11 +0300 Subject: [PATCH 79/83] Changed "[[gnu::fallthrough]]" to "// FALLTHRU" --- libraries/audio/src/InboundAudioStream.cpp | 4 +--- libraries/entities/src/EntityTree.cpp | 4 +--- libraries/networking/src/ResourceCache.cpp | 8 ++------ libraries/shared/src/Gzip.cpp | 4 +--- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index bbaedc003b..d60c5ba4ab 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -149,10 +149,8 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { lostAudioData(packetsDropped); // fall through to OnTime case -#if defined(__GNUC__) - [[gnu::fallthrough]]; -#endif } + // FALLTHRU case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer if (message.getType() == PacketType::SilentAudioFrame diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index e9e4f2631a..3149527216 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1402,9 +1402,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c case PacketType::EntityAdd: isAdd = true; // fall through to next case -#if defined(__GNUC__) - [[gnu::fallthrough]]; -#endif + // FALLTHRU case PacketType::EntityPhysics: case PacketType::EntityEdit: { quint64 startDecode = 0, endDecode = 0; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 3b8c9fdac0..d3583687e8 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -749,10 +749,8 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { case ResourceRequest::Result::Timeout: { qCDebug(networking) << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; // Fall through to other cases -#if defined(__GNUC__) - [[gnu::fallthrough]]; -#endif } + // FALLTHRU case ResourceRequest::Result::ServerUnavailable: { _attempts++; _attemptsRemaining--; @@ -770,10 +768,8 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { break; } // fall through to final failure -#if defined(__GNUC__) - [[gnu::fallthrough]]; -#endif } + // FALLTHRU default: { _attemptsRemaining = 0; qCDebug(networking) << "Error loading " << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining; diff --git a/libraries/shared/src/Gzip.cpp b/libraries/shared/src/Gzip.cpp index 44fe13f439..25e214fffb 100644 --- a/libraries/shared/src/Gzip.cpp +++ b/libraries/shared/src/Gzip.cpp @@ -60,9 +60,7 @@ bool gunzip(QByteArray source, QByteArray &destination) { switch (status) { case Z_NEED_DICT: status = Z_DATA_ERROR; -#if defined(__GNUC__) - [[gnu::fallthrough]]; -#endif + // FALLTHRU case Z_DATA_ERROR: case Z_MEM_ERROR: case Z_STREAM_ERROR: From 80f87f7a6264d6e06f1340771fe0345f5e180ea5 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 1 May 2018 15:29:22 -0700 Subject: [PATCH 80/83] Make resource swapchains immutable, fix for 14638 --- .../src/gpu/gl/GLBackendOutput.cpp | 4 ++-- .../src/gpu/gl/GLBackendPipeline.cpp | 7 +++---- libraries/gpu/src/gpu/ResourceSwapChain.h | 19 +++++++---------- .../render-utils/src/AntialiasingEffect.cpp | 21 +++++++++---------- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp index 2285b0e486..d1ab34da90 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendOutput.cpp @@ -46,10 +46,10 @@ void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffset) { - auto swapChain = batch._swapChains.get(batch._params[paramOffset]._uint); + auto swapChain = std::static_pointer_cast(batch._swapChains.get(batch._params[paramOffset]._uint)); if (swapChain) { auto index = batch._params[paramOffset + 1]._uint; - FramebufferPointer framebuffer = static_cast(swapChain.get())->get(index); + const auto& framebuffer = swapChain->get(index); setFramebuffer(framebuffer); } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 58fcc51605..91f1d8bb8c 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -268,7 +268,7 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si return; } - SwapChainPointer swapChain = batch._swapChains.get(batch._params[paramOffset + 0]._uint); + auto swapChain = std::static_pointer_cast(batch._swapChains.get(batch._params[paramOffset + 0]._uint)); if (!swapChain) { releaseResourceTexture(slot); @@ -276,9 +276,8 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si } auto index = batch._params[paramOffset + 2]._uint; auto renderBufferSlot = batch._params[paramOffset + 3]._uint; - FramebufferPointer resourceFramebuffer = static_cast(swapChain.get())->get(index); - TexturePointer resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); - + auto resourceFramebuffer = swapChain->get(index); + auto resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot); setResourceTexture(slot, resourceTexture); } diff --git a/libraries/gpu/src/gpu/ResourceSwapChain.h b/libraries/gpu/src/gpu/ResourceSwapChain.h index 7b46b35521..84e8ec7c74 100644 --- a/libraries/gpu/src/gpu/ResourceSwapChain.h +++ b/libraries/gpu/src/gpu/ResourceSwapChain.h @@ -15,18 +15,18 @@ namespace gpu { class SwapChain { public: - SwapChain(unsigned int size = 2U) : _size{ size } {} + SwapChain(size_t size = 2U) : _size{ size } {} virtual ~SwapChain() {} void advance() { _frontIndex = (_frontIndex + 1) % _size; } - unsigned int getSize() const { return _size; } + size_t getSize() const { return _size; } protected: - unsigned int _size; - unsigned int _frontIndex{ 0U }; + const size_t _size; + size_t _frontIndex{ 0U }; }; typedef std::shared_ptr SwapChainPointer; @@ -41,16 +41,13 @@ namespace gpu { using Type = R; using TypePointer = std::shared_ptr; + using TypeConstPointer = std::shared_ptr; - ResourceSwapChain(unsigned int size = 2U) : SwapChain{ size } {} - - void reset() { - for (auto& ptr : _resources) { - ptr.reset(); + ResourceSwapChain(const std::vector& v) : SwapChain{ std::min(v.size(), MAX_SIZE) } { + for (size_t i = 0; i < _size; ++i) { + _resources[i] = v[i]; } } - - TypePointer& edit(unsigned int index) { return _resources[(index + _frontIndex) % _size]; } const TypePointer& get(unsigned int index) const { return _resources[(index + _frontIndex) % _size]; } private: diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index dd4bda2e37..c9aa1b8f71 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -189,7 +189,6 @@ const int AntialiasingPass_NextMapSlot = 4; Antialiasing::Antialiasing(bool isSharpenEnabled) : _isSharpenEnabled{ isSharpenEnabled } { - _antialiasingBuffers = std::make_shared(2U); } Antialiasing::~Antialiasing() { @@ -325,25 +324,25 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const int width = sourceBuffer->getWidth(); int height = sourceBuffer->getHeight(); - if (_antialiasingBuffers->get(0)) { - if (_antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) {// || (sourceBuffer && (_antialiasingBuffer->getRenderBuffer(1) != sourceBuffer->getRenderBuffer(0)))) { - _antialiasingBuffers->edit(0).reset(); - _antialiasingBuffers->edit(1).reset(); - _antialiasingTextures[0].reset(); - _antialiasingTextures[1].reset(); - } + if (_antialiasingBuffers && _antialiasingBuffers->get(0) && _antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) { + _antialiasingBuffers.reset(); + _antialiasingTextures[0].reset(); + _antialiasingTextures[1].reset(); } - if (!_antialiasingBuffers->get(0)) { + + if (!_antialiasingBuffers) { + std::vector antiAliasingBuffers; // Link the antialiasing FBO to texture auto format = sourceBuffer->getRenderBuffer(0)->getTexelFormat(); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP); for (int i = 0; i < 2; i++) { - auto& antiAliasingBuffer = _antialiasingBuffers->edit(i); - antiAliasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); + antiAliasingBuffers.emplace_back(gpu::Framebuffer::create("antialiasing")); + const auto& antiAliasingBuffer = antiAliasingBuffers.back(); _antialiasingTextures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); antiAliasingBuffer->setRenderBuffer(0, _antialiasingTextures[i]); } + _antialiasingBuffers = std::make_shared(antiAliasingBuffers); } gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { From 64d442b281713262ff43868c3366e514a452bbfa Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 1 May 2018 16:54:26 -0700 Subject: [PATCH 81/83] Change type used for swap chain count --- libraries/gpu/src/gpu/ResourceSwapChain.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/gpu/src/gpu/ResourceSwapChain.h b/libraries/gpu/src/gpu/ResourceSwapChain.h index 84e8ec7c74..28c5ff2ed3 100644 --- a/libraries/gpu/src/gpu/ResourceSwapChain.h +++ b/libraries/gpu/src/gpu/ResourceSwapChain.h @@ -15,18 +15,18 @@ namespace gpu { class SwapChain { public: - SwapChain(size_t size = 2U) : _size{ size } {} + SwapChain(uint8_t size = 2U) : _size{ size } {} virtual ~SwapChain() {} void advance() { _frontIndex = (_frontIndex + 1) % _size; } - size_t getSize() const { return _size; } + uint8_t getSize() const { return _size; } protected: - const size_t _size; - size_t _frontIndex{ 0U }; + const uint8_t _size; + uint8_t _frontIndex{ 0U }; }; typedef std::shared_ptr SwapChainPointer; @@ -43,7 +43,7 @@ namespace gpu { using TypePointer = std::shared_ptr; using TypeConstPointer = std::shared_ptr; - ResourceSwapChain(const std::vector& v) : SwapChain{ std::min(v.size(), MAX_SIZE) } { + ResourceSwapChain(const std::vector& v) : SwapChain{ std::min((uint8_t)v.size(), MAX_SIZE) } { for (size_t i = 0; i < _size; ++i) { _resources[i] = v[i]; } From 4fa9af5534b483b7b53a794e9d0c36ea723c3a4f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 20 Apr 2018 11:38:25 -0700 Subject: [PATCH 82/83] Added items to the developer menu for debugging physics Hooked up Bullet's internal debug draw functionality to our client. Under the Developer > Physics Menu there are five new items: * Show Bullet Collision - will draw all collision shapes in wireframe. WARNING: can be slow on large scenes. * Show Bullet Bounding Boxes - will draw axis aligned bounding boxes around all physics shapes. * Show Bullet Contact Points - will draw all contact points where two or more objects are colliding. * Show Bullet Constraints - will render wire frame axes for each constraint connecting bodies together. * Show Bullet Constraint Limits - will render the joint limits for each constraint. --- interface/src/Application.cpp | 20 +++++++ interface/src/Application.h | 7 +++ interface/src/Menu.cpp | 6 +++ interface/src/Menu.h | 5 ++ libraries/physics/src/PhysicsDebugDraw.cpp | 54 +++++++++++++++++++ libraries/physics/src/PhysicsDebugDraw.h | 34 ++++++++++++ libraries/physics/src/PhysicsEngine.cpp | 55 ++++++++++++++++++++ libraries/physics/src/PhysicsEngine.h | 8 +++ libraries/render-utils/src/AnimDebugDraw.cpp | 12 ++--- 9 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 libraries/physics/src/PhysicsDebugDraw.cpp create mode 100644 libraries/physics/src/PhysicsDebugDraw.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cd4562da54..880f7a2213 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7828,6 +7828,26 @@ void Application::switchDisplayMode() { _previousHMDWornStatus = currentHMDWornStatus; } +void Application::setShowBulletWireframe(bool value) { + _physicsEngine->setShowBulletWireframe(value); +} + +void Application::setShowBulletAABBs(bool value) { + _physicsEngine->setShowBulletAABBs(value); +} + +void Application::setShowBulletContactPoints(bool value) { + _physicsEngine->setShowBulletContactPoints(value); +} + +void Application::setShowBulletConstraints(bool value) { + _physicsEngine->setShowBulletConstraints(value); +} + +void Application::setShowBulletConstraintLimits(bool value) { + _physicsEngine->setShowBulletConstraintLimits(value); +} + void Application::startHMDStandBySession() { _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 6d611bc8e2..ce7abf7099 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -243,6 +243,7 @@ public: bool isAboutToQuit() const { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } + PhysicsEnginePointer getPhysicsEngine() { return _physicsEngine; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display // rendering of several elements depend on that @@ -453,6 +454,12 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); + void setShowBulletWireframe(bool value); + void setShowBulletAABBs(bool value); + void setShowBulletContactPoints(bool value); + void setShowBulletConstraints(bool value); + void setShowBulletConstraintLimits(bool value); + private: void init(); bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 50ff65ad1a..60d5abf260 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -735,6 +735,12 @@ Menu::Menu() { } addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls, 0, false, qApp->getEntities().data(), SIGNAL(setRenderDebugHulls())); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletWireframe, 0, false, qApp, SLOT(setShowBulletWireframe(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletAABBs, 0, false, qApp, SLOT(setShowBulletAABBs(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletContactPoints, 0, false, qApp, SLOT(setShowBulletContactPoints(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool))); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool))); + // Developer > Ask to Reset Settings addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AskToResetSettings, 0, false); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c8c8ee42df..20375a71b2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -143,6 +143,11 @@ namespace MenuOption { const QString PhysicsShowHulls = "Draw Collision Shapes"; const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString VerboseLogging = "Verbose Logging"; + const QString PhysicsShowBulletWireframe = "Show Bullet Collision"; + const QString PhysicsShowBulletAABBs = "Show Bullet Bounding Boxes"; + const QString PhysicsShowBulletContactPoints = "Show Bullet Contact Points"; + const QString PhysicsShowBulletConstraints = "Show Bullet Constraints"; + const QString PhysicsShowBulletConstraintLimits = "Show Bullet Constraint Limits"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; const QString Preferences = "General..."; const QString Quit = "Quit"; diff --git a/libraries/physics/src/PhysicsDebugDraw.cpp b/libraries/physics/src/PhysicsDebugDraw.cpp new file mode 100644 index 0000000000..b2911219d5 --- /dev/null +++ b/libraries/physics/src/PhysicsDebugDraw.cpp @@ -0,0 +1,54 @@ +// +// PhysicsDebugDraw.cpp +// libraries/physics/src +// +// Created by Anthony Thibault 2018-4-18 +// 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 "PhysicsDebugDraw.h" +#include "BulletUtil.h" +#include "PhysicsLogging.h" + +#include +#include + +void PhysicsDebugDraw::drawLine(const btVector3& from, const btVector3& to, const btVector3& color) { + DebugDraw::getInstance().drawRay(bulletToGLM(from), bulletToGLM(to), glm::vec4(color.getX(), color.getY(), color.getZ(), 1.0f)); +} + +void PhysicsDebugDraw::drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, + int lifeTime, const btVector3& color) { + + glm::vec3 normal, tangent, biNormal; + generateBasisVectors(bulletToGLM(normalOnB), Vectors::UNIT_X, normal, tangent, biNormal); + btVector3 u = glmToBullet(normal); + btVector3 v = glmToBullet(tangent); + btVector3 w = glmToBullet(biNormal); + + // x marks the spot, green is along the normal. + const float CONTACT_POINT_RADIUS = 0.1f; + const btVector3 GREEN(0.0f, 1.0f, 0.0f); + const btVector3 WHITE(1.0f, 1.0f, 1.0f); + drawLine(PointOnB - u * CONTACT_POINT_RADIUS, PointOnB + u * CONTACT_POINT_RADIUS, GREEN); + drawLine(PointOnB - v * CONTACT_POINT_RADIUS, PointOnB + v * CONTACT_POINT_RADIUS, WHITE); + drawLine(PointOnB - w * CONTACT_POINT_RADIUS, PointOnB + w * CONTACT_POINT_RADIUS, WHITE); +} + +void PhysicsDebugDraw::reportErrorWarning(const char* warningString) { + qCWarning(physics) << "BULLET:" << warningString; +} + +void PhysicsDebugDraw::draw3dText(const btVector3& location, const char* textString) { +} + +void PhysicsDebugDraw::setDebugMode(int debugMode) { + _debugDrawMode = debugMode; +} + +int PhysicsDebugDraw::getDebugMode() const { + return _debugDrawMode; +} diff --git a/libraries/physics/src/PhysicsDebugDraw.h b/libraries/physics/src/PhysicsDebugDraw.h new file mode 100644 index 0000000000..f653136474 --- /dev/null +++ b/libraries/physics/src/PhysicsDebugDraw.h @@ -0,0 +1,34 @@ +// +// PhysicsDebugDraw.h +// libraries/physics/src +// +// Created by Anthony Thibault 2018-4-18 +// 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 +// +// http://bulletphysics.org/Bullet/BulletFull/classbtIDebugDraw.html + +#ifndef hifi_PhysicsDebugDraw_h +#define hifi_PhysicsDebugDraw_h + +#include +#include + +class PhysicsDebugDraw : public btIDebugDraw { +public: + using btIDebugDraw::drawLine; + virtual void drawLine(const btVector3& from, const btVector3& to, const btVector3& color) override; + virtual void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, + int lifeTime, const btVector3& color) override; + virtual void reportErrorWarning(const char* warningString) override; + virtual void draw3dText(const btVector3& location, const char* textString) override; + virtual void setDebugMode(int debugMode) override; + virtual int getDebugMode() const override; + +protected: + uint32_t _debugDrawMode; +}; + +#endif // hifi_PhysicsDebugDraw_h diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 7c2ad55405..50d516c256 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -22,6 +22,7 @@ #include "ObjectMotionState.h" #include "PhysicsEngine.h" #include "PhysicsHelpers.h" +#include "PhysicsDebugDraw.h" #include "ThreadSafeDynamicsWorld.h" #include "PhysicsLogging.h" @@ -49,6 +50,10 @@ void PhysicsEngine::init() { _broadphaseFilter = new btDbvtBroadphase(); _constraintSolver = new btSequentialImpulseConstraintSolver; _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig); + _physicsDebugDraw.reset(new PhysicsDebugDraw()); + + // hook up debug draw renderer + _dynamicsWorld->setDebugDrawer(_physicsDebugDraw.get()); _ghostPairCallback = new btGhostPairCallback(); _dynamicsWorld->getPairCache()->setInternalGhostPairCallback(_ghostPairCallback); @@ -333,6 +338,10 @@ void PhysicsEngine::stepSimulation() { _hasOutgoingChanges = true; } + + if (_physicsDebugDraw->getDebugMode()) { + _dynamicsWorld->debugDrawWorld(); + } } class CProfileOperator { @@ -785,3 +794,49 @@ void PhysicsEngine::forEachDynamic(std::function act } } } + +void PhysicsEngine::setShowBulletWireframe(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawWireframe); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawWireframe); + } +} + +void PhysicsEngine::setShowBulletAABBs(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawAabb); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawAabb); + } +} + +void PhysicsEngine::setShowBulletContactPoints(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawContactPoints); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawContactPoints); + } +} + +void PhysicsEngine::setShowBulletConstraints(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawConstraints); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawConstraints); + } +} + +void PhysicsEngine::setShowBulletConstraintLimits(bool value) { + int mode = _physicsDebugDraw->getDebugMode(); + if (value) { + _physicsDebugDraw->setDebugMode(mode | btIDebugDraw::DBG_DrawConstraintLimits); + } else { + _physicsDebugDraw->setDebugMode(mode & ~btIDebugDraw::DBG_DrawConstraintLimits); + } +} + diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 92d2e6885a..0dfe3a7a7c 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -30,6 +30,7 @@ const float HALF_SIMULATION_EXTENT = 512.0f; // meters class CharacterController; +class PhysicsDebugDraw; // simple class for keeping track of contacts class ContactKey { @@ -96,6 +97,12 @@ public: void removeDynamic(const QUuid dynamicID); void forEachDynamic(std::function actor); + void setShowBulletWireframe(bool value); + void setShowBulletAABBs(bool value); + void setShowBulletContactPoints(bool value); + void setShowBulletConstraints(bool value); + void setShowBulletConstraintLimits(bool value); + private: QList removeDynamicsForBody(btRigidBody* body); void addObjectToDynamicsWorld(ObjectMotionState* motionState); @@ -114,6 +121,7 @@ private: btSequentialImpulseConstraintSolver* _constraintSolver = NULL; ThreadSafeDynamicsWorld* _dynamicsWorld = NULL; btGhostPairCallback* _ghostPairCallback = NULL; + std::unique_ptr _physicsDebugDraw; ContactMap _contactMap; CollisionEvents _collisionEvents; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 7086b65f4c..eca500f36c 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -52,9 +52,9 @@ public: batch.setInputFormat(_vertexFormat); batch.setInputBuffer(0, _vertexBuffer, 0, sizeof(Vertex)); - batch.setIndexBuffer(gpu::UINT16, _indexBuffer, 0); + batch.setIndexBuffer(gpu::UINT32, _indexBuffer, 0); - auto numIndices = _indexBuffer->getSize() / sizeof(uint16_t); + auto numIndices = _indexBuffer->getSize() / sizeof(uint32_t); batch.drawIndexed(gpu::LINES, (int)numIndices); } @@ -135,9 +135,9 @@ AnimDebugDraw::AnimDebugDraw() : AnimDebugDrawData::Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(0, 0, 255, 255) }, AnimDebugDrawData::Vertex { glm::vec3(1.0, 1.0f, 2.0f), toRGBA(0, 0, 255, 255) }, }); - static std::vector indices({ 0, 1, 2, 3, 4, 5 }); + static std::vector indices({ 0, 1, 2, 3, 4, 5 }); _animDebugDrawData->_vertexBuffer->setSubData(0, vertices); - _animDebugDrawData->_indexBuffer->setSubData(0, indices); + _animDebugDrawData->_indexBuffer->setSubData(0, indices); } AnimDebugDraw::~AnimDebugDraw() { @@ -425,9 +425,9 @@ void AnimDebugDraw::update() { data._isVisible = (numVerts > 0); - data._indexBuffer->resize(sizeof(uint16_t) * numVerts); + data._indexBuffer->resize(sizeof(uint32_t) * numVerts); for (int i = 0; i < numVerts; i++) { - data._indexBuffer->setSubData(i, (uint16_t)i);; + data._indexBuffer->setSubData(i, (uint32_t)i);; } }); scene->enqueueTransaction(transaction); From 689fbd2da07aeb30f698e06b4b7d166367aac8fe Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 2 May 2018 11:09:17 -0700 Subject: [PATCH 83/83] added requested changes --- interface/src/Application.cpp | 30 ++++++++----------- interface/src/ModelPackager.cpp | 1 - interface/src/avatar/MyAvatar.cpp | 5 ++++ interface/src/avatar/MyAvatar.h | 2 ++ libraries/fbx/src/FSTReader.cpp | 16 +++++----- .../src/model-networking/ModelCache.cpp | 6 ++-- 6 files changed, 29 insertions(+), 31 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec40ffc653..b384e1086e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1231,7 +1231,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { getEntities()->reloadEntityScripts(); - loadAvatarScripts(getMyAvatar()->getSkeletonModel()->getFBXGeometry().scripts); + loadAvatarScripts(getMyAvatar()->getScriptUrls()); }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptLoadError, @@ -4728,16 +4728,14 @@ void Application::init() { } void Application::loadAvatarScripts(const QVector& urls) { - if (urls.size() > 0) { - auto scriptEngines = DependencyManager::get(); - auto runningScripts = scriptEngines->getRunningScripts(); - for (auto url : urls) { - int index = runningScripts.indexOf(url); - if (index < 0) { - auto scriptEnginePointer = scriptEngines->loadScript(url, false); - if (scriptEnginePointer) { - scriptEnginePointer->setType(ScriptEngine::Type::AVATAR); - } + auto scriptEngines = DependencyManager::get(); + auto runningScripts = scriptEngines->getRunningScripts(); + for (auto url : urls) { + int index = runningScripts.indexOf(url); + if (index < 0) { + auto scriptEnginePointer = scriptEngines->loadScript(url, false); + if (scriptEnginePointer) { + scriptEnginePointer->setType(ScriptEngine::Type::AVATAR); } } } @@ -4746,12 +4744,10 @@ void Application::loadAvatarScripts(const QVector& urls) { void Application::unloadAvatarScripts() { auto scriptEngines = DependencyManager::get(); auto urls = scriptEngines->getRunningScripts(); - if (urls.size() > 0) { - for (auto url : urls) { - auto scriptEngine = scriptEngines->getScriptEngine(url); - if (scriptEngine->getType() == ScriptEngine::Type::AVATAR) { - scriptEngines->stopScript(url, false); - } + for (auto url : urls) { + auto scriptEngine = scriptEngines->getScriptEngine(url); + if (scriptEngine->getType() == ScriptEngine::Type::AVATAR) { + scriptEngines->stopScript(url, false); } } } diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 72b99ce5a3..0e34eebc80 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -210,7 +210,6 @@ bool ModelPackager::zipModel() { _mapping[TEXDIR_FIELD] = tempDir.relativeFilePath(texDir.path()); for (auto multi : _mapping.values(SCRIPT_FIELD)) { - multi.fromValue(tempDir.relativeFilePath(scriptDir.path()) + multi.toString()); } // Copy FST diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 85b2ece077..5ce056e9b7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2852,6 +2852,11 @@ void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } +QVector MyAvatar::getScriptUrls() { + QVector scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector(); + return scripts; +} + glm::vec3 MyAvatar::getPositionForAudio() { glm::vec3 result; switch (_audioListenerMode) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2bcbd878a9..d605e1cc44 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -594,6 +594,8 @@ public: void setWalkSpeed(float value); float getWalkSpeed() const; + QVector getScriptUrls(); + public slots: void increaseSize(); void decreaseSize(); diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index fcbfca1e7d..d63a5b3cc4 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -193,17 +193,15 @@ QVector FSTReader::getScripts(const QUrl& url, const QVariantHash& mapp QVector scriptPaths; if (!fstMapping.value(SCRIPT_FIELD).isNull()) { auto scripts = fstMapping.values(SCRIPT_FIELD).toVector(); - if (scripts.size() > 0) { - for (auto &script : scripts) { - QString scriptPath = script.toString(); - if (QUrl(scriptPath).isRelative()) { - if (scriptPath.at(0) == '/') { - scriptPath = scriptPath.right(scriptPath.length() - 1); - } - scriptPath = url.resolved(QUrl(scriptPath)).toString(); + for (auto &script : scripts) { + QString scriptPath = script.toString(); + if (QUrl(scriptPath).isRelative()) { + if (scriptPath.at(0) == '/') { + scriptPath = scriptPath.right(scriptPath.length() - 1); } - scriptPaths.push_back(scriptPath); + scriptPath = url.resolved(QUrl(scriptPath)).toString(); } + scriptPaths.push_back(scriptPath); } } return scriptPaths; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index e1086bc0c9..416920d43f 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -221,10 +221,8 @@ void GeometryReader::run() { // Add scripts to fbxgeometry if (!_mapping.value(SCRIPT_FIELD).isNull()) { QVariantList scripts = _mapping.values(SCRIPT_FIELD); - if (scripts.size() > 0) { - for (auto &script : scripts) { - fbxGeometry->scripts.push_back(script.toString()); - } + for (auto &script : scripts) { + fbxGeometry->scripts.push_back(script.toString()); } }