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

This commit is contained in:
David Back 2018-07-06 18:01:16 -07:00
commit d04c2eaf73
40 changed files with 1848 additions and 214 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Toolbar
android:id="@+id/toolbar_actionbar"
android:layout_width="match_parent"
@ -16,19 +17,9 @@
android:contentInsetStartWithNavigation="0dp"
android:title="">
</Toolbar>
<WebView
android:id="@+id/web_view"
<FrameLayout
android:id="@+id/content_frame"
android:layout_below="@id/toolbar_actionbar"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/toolbarProgressBar"
android:layout_below="@id/toolbar_actionbar"
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="3dp"
android:indeterminate="false"
android:padding="0dp" />
android:layout_height="match_parent" />
</RelativeLayout>

View file

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

View file

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

Binary file not shown.

View file

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

After

(image error) Size: 3.3 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -715,7 +715,7 @@ void MyAvatar::simulate(float deltaTime) {
}
});
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
_characterController.setFlyingAllowed(zoneAllowsFlying && (_enableFlying || !isPhysicsEnabled));
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
_characterController.setCollisionlessAllowed(collisionlessAllowed);
}

View file

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

View file

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

View file

@ -137,8 +137,13 @@ void WindowScriptingInterface::openUrl(const QUrl& url) {
if (url.scheme() == URL_SCHEME_HIFI) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else {
#if defined(Q_OS_ANDROID)
QList<QString> args = { url.toString() };
AndroidHelper::instance().requestActivity("WebView", true, args);
#else
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
#endif
}
}
}

View file

@ -852,17 +852,16 @@ void AddressManager::refreshPreviousLookup() {
void AddressManager::copyAddress() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "copyAddress");
QMetaObject::invokeMethod(qApp, "copyToClipboard", Q_ARG(QString, currentShareableAddress().toString()));
return;
}
// assume that the address is being copied because the user wants a shareable address
QGuiApplication::clipboard()->setText(currentShareableAddress().toString());
}
void AddressManager::copyPath() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "copyPath");
QMetaObject::invokeMethod(qApp, "copyToClipboard", Q_ARG(QString, currentPath()));
return;
}

View file

@ -475,13 +475,16 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
}
}
void DomainHandler::sentCheckInPacket() {
bool DomainHandler::checkInPacketTimeout() {
++_checkInPacketsSinceLastReply;
if (_checkInPacketsSinceLastReply >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
if (_checkInPacketsSinceLastReply > MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
// so emit our signal that says that
qCDebug(networking) << "Limit of silent domain checkins reached";
emit limitOfSilentDomainCheckInsReached();
return true;
} else {
return false;
}
}

View file

@ -96,7 +96,7 @@ public:
void softReset();
int getCheckInPacketsSinceLastReply() const { return _checkInPacketsSinceLastReply; }
void sentCheckInPacket();
bool checkInPacketTimeout();
void clearPendingCheckins() { _checkInPacketsSinceLastReply = 0; }
/**jsdoc

View file

@ -305,7 +305,8 @@ void NodeList::sendDomainServerCheckIn() {
} else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) {
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
handleICEConnectionToDomainServer();
} else if (!_domainHandler.getIP().isNull()) {
// let the domain handler know we are due to send a checkin packet
} else if (!_domainHandler.getIP().isNull() && !_domainHandler.checkInPacketTimeout()) {
PacketType domainPacketType = !_domainHandler.isConnected()
? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
@ -419,10 +420,15 @@ void NodeList::sendDomainServerCheckIn() {
flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn);
// Send duplicate check-ins in the exponentially increasing sequence 1, 1, 2, 4, ...
int outstandingCheckins = _domainHandler.getCheckInPacketsSinceLastReply();
int checkinCount = outstandingCheckins > 1 ? std::pow(2, outstandingCheckins - 2) : 1;
for (int i = 1; i < checkinCount; ++i) {
auto packetCopy = domainPacket->createCopy(*domainPacket);
sendPacket(std::move(packetCopy), _domainHandler.getSockAddr());
}
sendPacket(std::move(domainPacket), _domainHandler.getSockAddr());
// let the domain handler know we sent another check in or connect packet
_domainHandler.sentCheckInPacket();
}
}

View file

@ -379,9 +379,6 @@ void CharacterController::setState(State desiredState, const char* reason) {
#else
void CharacterController::setState(State desiredState) {
#endif
if (!_flyingAllowed && desiredState == State::Hover) {
desiredState = State::InAir;
}
if (desiredState != _state) {
#ifdef DEBUG_STATE_CHANGE
@ -747,7 +744,7 @@ void CharacterController::updateState() {
const float JUMP_SPEED = _scaleFactor * DEFAULT_AVATAR_JUMP_SPEED;
if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
SET_STATE(State::Ground, "hit ground");
} else {
} else if (_flyingAllowed) {
btVector3 desiredVelocity = _targetVelocity;
if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) {
desiredVelocity = btVector3(0.0f, 0.0f, 0.0f);
@ -761,14 +758,17 @@ void CharacterController::updateState() {
// Transition to hover if we are above the fall threshold
SET_STATE(State::Hover, "above fall threshold");
}
} else if (!rayHasHit && !_hasSupport) {
SET_STATE(State::Hover, "no ground detected");
}
break;
}
case State::Hover:
btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length();
bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f);
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
if (!_flyingAllowed && rayHasHit) {
SET_STATE(State::InAir, "flying not allowed");
} else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
SET_STATE(State::InAir, "near ground");
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
SET_STATE(State::Ground, "touching ground");
@ -827,9 +827,6 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio
void CharacterController::setFlyingAllowed(bool value) {
if (value != _flyingAllowed) {
_flyingAllowed = value;
if (!_flyingAllowed && _state == State::Hover) {
SET_STATE(State::InAir, "flying not allowed");
}
}
}

View file

@ -393,9 +393,6 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
_sharedObject->setRootItem(newItem);
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
onItemCreated(qmlContext, newItem);
if (!rootCreated) {
@ -405,6 +402,8 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
// Call this callback after rootitem is set, otherwise VrMenu wont work
callback(qmlContext, newItem);
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
}
QQmlContext* OffscreenSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) {

View file

@ -0,0 +1,291 @@
//
// InteractiveWindow.cpp
// libraries/ui/src
//
// Created by Thijs Wenker on 2018-06-25
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "InteractiveWindow.h"
#include <QtQml/QQmlContext>
#include <QtCore/QThread>
#include <DependencyManager.h>
#include <RegisteredMetaTypes.h>
#include "OffscreenUi.h"
#include "shared/QtHelpers.h"
static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml");
static const char* const FLAGS_PROPERTY = "flags";
static const char* const SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const char* const POSITION_PROPERTY = "position";
static const char* const INTERACTIVE_WINDOW_POSITION_PROPERTY = "interactiveWindowPosition";
static const char* const SIZE_PROPERTY = "size";
static const char* const INTERACTIVE_WINDOW_SIZE_PROPERTY = "interactiveWindowSize";
static const char* const VISIBLE_PROPERTY = "visible";
static const char* const INTERACTIVE_WINDOW_VISIBLE_PROPERTY = "interactiveWindowVisible";
static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";
static const char* const PRESENTATION_MODE_PROPERTY = "presentationMode";
static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc";
void registerInteractiveWindowMetaType(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue);
}
QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in) {
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
}
void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out) {
if (const auto interactiveWindow = qobject_cast<InteractiveWindowPointer>(object.toQObject())) {
out = interactiveWindow;
}
}
InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
_qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
if (properties.contains(FLAGS_PROPERTY)) {
object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt());
}
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt());
}
if (properties.contains(TITLE_PROPERTY)) {
object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
}
if (properties.contains(SIZE_PROPERTY)) {
const auto size = vec2FromVariant(properties[SIZE_PROPERTY]);
object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
}
if (properties.contains(POSITION_PROPERTY)) {
const auto position = vec2FromVariant(properties[POSITION_PROPERTY]);
object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
}
if (properties.contains(VISIBLE_PROPERTY)) {
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
}
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection);
connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection);
connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection);
QUrl sourceURL{ sourceUrl };
// If the passed URL doesn't correspond to a known scheme, assume it's a local file path
if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) {
sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString();
}
object->setProperty(SOURCE_PROPERTY, sourceURL);
});
}
InteractiveWindow::~InteractiveWindow() {
close();
}
void InteractiveWindow::sendToQml(const QVariant& message) {
// Forward messages received from the script on to QML
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
} else {
emit scriptEventReceived(scriptMessage);
}
}
void InteractiveWindow::emitWebEvent(const QVariant& webMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
} else {
emit webEventReceived(webMessage);
}
}
void InteractiveWindow::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close");
return;
}
if (_qmlWindow) {
_qmlWindow->deleteLater();
}
_qmlWindow = nullptr;
}
void InteractiveWindow::show() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "show");
return;
}
if (_qmlWindow) {
QMetaObject::invokeMethod(_qmlWindow, "show", Qt::DirectConnection);
}
}
void InteractiveWindow::raise() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "raise");
return;
}
if (_qmlWindow) {
QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::DirectConnection);
}
}
void InteractiveWindow::qmlToScript(const QVariant& message) {
if (message.canConvert<QJSValue>()) {
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
} else if (message.canConvert<QString>()) {
emit fromQml(message.toString());
} else {
qWarning() << "Unsupported message type " << message;
}
}
void InteractiveWindow::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Q_ARG(bool, visible));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(INTERACTIVE_WINDOW_VISIBLE_PROPERTY, visible);
}
}
bool InteractiveWindow::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result = false;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "isVisible", Q_RETURN_ARG(bool, result));
return result;
}
if (_qmlWindow.isNull()) {
return false;
}
return _qmlWindow->property(INTERACTIVE_WINDOW_VISIBLE_PROPERTY).toBool();
}
glm::vec2 InteractiveWindow::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPosition", Q_RETURN_ARG(glm::vec2, result));
return result;
}
if (_qmlWindow.isNull()) {
return {};
}
return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_POSITION_PROPERTY).toPointF());
}
void InteractiveWindow::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Q_ARG(const glm::vec2&, position));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowPositionForMode", Qt::DirectConnection);
}
}
glm::vec2 InteractiveWindow::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getSize", Q_RETURN_ARG(glm::vec2, result));
return result;
}
if (_qmlWindow.isNull()) {
return {};
}
return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_SIZE_PROPERTY).toSize());
}
void InteractiveWindow::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Q_ARG(const glm::vec2&, size));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowSizeForMode", Qt::DirectConnection);
}
}
QString InteractiveWindow::getTitle() const {
if (QThread::currentThread() != thread()) {
QString result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getTitle", Q_RETURN_ARG(QString, result));
return result;
}
if (_qmlWindow.isNull()) {
return QString();
}
return _qmlWindow->property(TITLE_PROPERTY).toString();
}
void InteractiveWindow::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Q_ARG(const QString&, title));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
}
int InteractiveWindow::getPresentationMode() const {
if (QThread::currentThread() != thread()) {
int result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPresentationMode",
Q_RETURN_ARG(int, result));
return result;
}
if (_qmlWindow.isNull()) {
return Virtual;
}
return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt();
}
void InteractiveWindow::setPresentationMode(int presentationMode) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(PRESENTATION_MODE_PROPERTY, presentationMode);
}
}

View file

@ -0,0 +1,206 @@
//
// InteractiveWindow.h
// libraries/ui/src
//
// Created by Thijs Wenker on 2018-06-25
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_InteractiveWindow_h
#define hifi_InteractiveWindow_h
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtScript/QScriptValue>
#include <QQmlEngine>
#include <glm/glm.hpp>
#include <GLMHelpers.h>
namespace InteractiveWindowEnums {
Q_NAMESPACE
enum InteractiveWindowFlags : uint8_t {
AlwaysOnTop = 1 << 0,
CloseButtonHides = 1 << 1
};
Q_ENUM_NS(InteractiveWindowFlags);
enum InteractiveWindowPresentationMode {
Virtual,
Native
};
Q_ENUM_NS(InteractiveWindowPresentationMode);
}
using namespace InteractiveWindowEnums;
/**jsdoc
* @class InteractiveWindow
*
* @hifi-interface
* @hifi-client-en
*
* @property {string} title
* @property {Vec2} position
* @property {Vec2} size
* @property {boolean} visible
* @property {Desktop.PresentationMode} presentationMode
*
*/
class InteractiveWindow : public QObject {
Q_OBJECT
Q_PROPERTY(QString title READ getTitle WRITE setTitle)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible)
Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode)
public:
InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties);
~InteractiveWindow();
private:
// define property getters and setters as private to not expose them to the JS API
Q_INVOKABLE QString getTitle() const;
Q_INVOKABLE void setTitle(const QString& title);
Q_INVOKABLE glm::vec2 getPosition() const;
Q_INVOKABLE void setPosition(const glm::vec2& position);
Q_INVOKABLE glm::vec2 getSize() const;
Q_INVOKABLE void setSize(const glm::vec2& size);
Q_INVOKABLE void setVisible(bool visible);
Q_INVOKABLE bool isVisible() const;
Q_INVOKABLE void setPresentationMode(int presentationMode);
Q_INVOKABLE int getPresentationMode() const;
public slots:
/**jsdoc
* @function InteractiveWindow.sendToQml
* @param {object} message
*/
// Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message);
/**jsdoc
* @function InteractiveWindow.emitScriptEvent
* @param {object} message
*/
// QmlWindow content may include WebView requiring EventBridge.
void emitScriptEvent(const QVariant& scriptMessage);
/**jsdoc
* @function InteractiveWindow.emitWebEvent
* @param {object} message
*/
void emitWebEvent(const QVariant& webMessage);
/**jsdoc
* @function InteractiveWindow.close
*/
Q_INVOKABLE void close();
/**jsdoc
* @function InteractiveWindow.show
*/
Q_INVOKABLE void show();
/**jsdoc
* @function InteractiveWindow.raise
*/
Q_INVOKABLE void raise();
signals:
/**jsdoc
* @function InteractiveWindow.visibleChanged
* @returns {Signal}
*/
void visibleChanged();
/**jsdoc
* @function InteractiveWindow.positionChanged
* @returns {Signal}
*/
void positionChanged();
/**jsdoc
* @function InteractiveWindow.sizeChanged
* @returns {Signal}
*/
void sizeChanged();
/**jsdoc
* @function InteractiveWindow.presentationModeChanged
* @returns {Signal}
*/
void presentationModeChanged();
/**jsdoc
* @function InteractiveWindow.titleChanged
* @returns {Signal}
*/
void titleChanged();
/**jsdoc
* @function InteractiveWindow.closed
* @returns {Signal}
*/
void closed();
/**jsdoc
* @function InteractiveWindow.fromQml
* @param {object} message
* @returns {Signal}
*/
// Scripts can connect to this signal to receive messages from the QML object
void fromQml(const QVariant& message);
/**jsdoc
* @function InteractiveWindow.scriptEventReceived
* @param {object} message
* @returns {Signal}
*/
// InteractiveWindow content may include WebView requiring EventBridge.
void scriptEventReceived(const QVariant& message);
/**jsdoc
* @function InteractiveWindow.webEventReceived
* @param {object} message
* @returns {Signal}
*/
void webEventReceived(const QVariant& message);
protected slots:
/**jsdoc
* @function InteractiveWindow.qmlToScript
* @param {object} message
* @returns {Signal}
*/
void qmlToScript(const QVariant& message);
private:
QPointer<QObject> _qmlWindow;
};
typedef InteractiveWindow* InteractiveWindowPointer;
QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in);
void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out);
void registerInteractiveWindowMetaType(QScriptEngine* engine);
Q_DECLARE_METATYPE(InteractiveWindowPointer)
#endif // hifi_InteractiveWindow_h

View file

@ -46,7 +46,7 @@ void Space::processResets(const Transaction::Resets& transactions) {
auto proxyID = std::get<0>(reset);
// Guard against proxyID being past the end of the list.
if (proxyID < 0 || proxyID >= (int32_t)_proxies.size() || proxyID >= (int32_t)_owners.size()) {
if (!_IDAllocator.checkIndex(proxyID)) {
continue;
}
auto& item = _proxies[proxyID];
@ -61,6 +61,9 @@ void Space::processResets(const Transaction::Resets& transactions) {
void Space::processRemoves(const Transaction::Removes& transactions) {
for (auto removedID : transactions) {
if (!_IDAllocator.checkIndex(removedID)) {
continue;
}
_IDAllocator.freeIndex(removedID);
// Access the true item
@ -75,7 +78,7 @@ void Space::processRemoves(const Transaction::Removes& transactions) {
void Space::processUpdates(const Transaction::Updates& transactions) {
for (auto& update : transactions) {
auto updateID = std::get<0>(update);
if (updateID == INVALID_PROXY_ID) {
if (!_IDAllocator.checkIndex(updateID)) {
continue;
}
@ -141,6 +144,7 @@ uint8_t Space::getRegion(int32_t proxyID) const {
}
void Space::clear() {
Collection::clear();
std::unique_lock<std::mutex> lock(_proxiesMutex);
_IDAllocator.clear();
_proxies.clear();

View file

@ -42,7 +42,7 @@ public:
uint32_t getNumViews() const { return (uint32_t)(_views.size()); }
void copyViews(std::vector<View>& copy) const;
uint32_t getNumObjects() const { return _IDAllocator.getNumLiveIndices(); } // (uint32_t)(_proxies.size() - _freeIndices.size()); }
uint32_t getNumObjects() const { return _IDAllocator.getNumLiveIndices(); }
uint32_t getNumAllocatedProxies() const { return (uint32_t)(_IDAllocator.getNumAllocatedIndices()); }
void categorizeAndGetChanges(std::vector<Change>& changes);
@ -51,7 +51,7 @@ public:
const Owner getOwner(int32_t proxyID) const;
uint8_t getRegion(int32_t proxyID) const;
void clear();
void clear() override;
private:
void processTransactionFrame(const Transaction& transaction) override;

View file

@ -111,6 +111,12 @@ Collection::Collection() {
Collection::~Collection() {
}
void Collection::clear() {
std::unique_lock<std::mutex> lock(_transactionQueueMutex);
_transactionQueue.clear();
_transactionFrames.clear();
}
ProxyID Collection::allocateID() {
// Just increment and return the previous value initialized at 0
return _IDAllocator.allocateIndex();

View file

@ -137,6 +137,8 @@ public:
Collection();
~Collection();
virtual void clear();
// This call is thread safe, can be called from anywhere to allocate a new ID
ProxyID allocateID();

View file

@ -0,0 +1,34 @@
//
// interactiveWindowTest.js
//
// Created by Thijs Wenker on 2018-07-03
// Copyright 2018 High Fidelity, Inc.
//
// An example of an interactive window that toggles presentation mode when toggling HMD on/off
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
function getPreferredPresentationMode() {
return HMD.active ? Desktop.PresentationMode.VIRTUAL : Desktop.PresentationMode.NATIVE;
}
function getPreferredTitle() {
return HMD.active ? 'Virtual Desktop Window' : 'Native Desktop Window';
}
var virtualWindow = Desktop.createWindow(Script.resourcesPath() + 'qml/OverlayWindowTest.qml', {
title: getPreferredTitle(),
flags: Desktop.ALWAYS_ON_TOP,
presentationMode: getPreferredPresentationMode(),
size: {x: 500, y: 400}
});
HMD.displayModeChanged.connect(function() {
virtualWindow.presentationMode = getPreferredPresentationMode();
virtualWindow.title = getPreferredTitle();
});
Script.scriptEnding.connect(function() {
virtualWindow.close();
});

View file

@ -0,0 +1,106 @@
"use strict";
//
// clickWeb.js
// scripts/system/+android
//
// Created by Gabriel Calero & Cristian Duarte on Jun 22, 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 logEnabled = false;
var touchOverlayID;
var touchEntityID;
function printd(str) {
if (logEnabled)
print("[clickWeb.js] " + str);
}
function intersectsWebOverlay(intersection) {
return intersection && intersection.intersects && intersection.overlayID &&
Overlays.getOverlayType(intersection.overlayID) == "web3d";
}
function intersectsWebEntity(intersection) {
if (intersection && intersection.intersects && intersection.entityID) {
var properties = Entities.getEntityProperties(intersection.entityID, ["type", "sourceUrl"]);
return properties.type && properties.type == "Web" && properties.sourceUrl;
}
return false;
}
function findRayIntersection(pickRay) {
// Check 3D overlays and entities. Argument is an object with origin and direction.
var overlayRayIntersection = Overlays.findRayIntersection(pickRay);
var entityRayIntersection = Entities.findRayIntersection(pickRay, true);
var isOverlayInters = intersectsWebOverlay(overlayRayIntersection);
var isEntityInters = intersectsWebEntity(entityRayIntersection);
if (isOverlayInters &&
(!isEntityInters ||
overlayRayIntersection.distance < entityRayIntersection.distance)) {
return { type: 'overlay', obj: overlayRayIntersection };
} else if (isEntityInters &&
(!isOverlayInters ||
entityRayIntersection.distance < overlayRayIntersection.distance)) {
return { type: 'entity', obj: entityRayIntersection };
}
return false;
}
function touchBegin(event) {
var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y));
if (intersection && intersection.type == 'overlay') {
touchOverlayID = intersection.obj.overlayID;
touchEntityID = null;
} else if (intersection && intersection.type == 'entity') {
touchEntityID = intersection.obj.entityID;
touchOverlayID = null;
}
}
function touchEnd(event) {
var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y));
if (intersection && intersection.type == 'overlay' && touchOverlayID == intersection.obj.overlayID) {
var propertiesToGet = {};
propertiesToGet[overlayID] = ['url'];
var properties = Overlays.getOverlaysProperties(propertiesToGet);
if (properties[overlayID].url) {
Window.openUrl(properties[overlayID].url);
}
} else if (intersection && intersection.type == 'entity' && touchEntityID == intersection.obj.entityID) {
var properties = Entities.getEntityProperties(touchEntityID, ["sourceUrl"]);
if (properties.sourceUrl) {
Window.openUrl(properties.sourceUrl);
}
}
touchOverlayID = null;
touchEntityID = null;
}
function ending() {
Controller.touchBeginEvent.disconnect(touchBegin);
Controller.touchEndEvent.disconnect(touchEnd);
}
function init() {
Controller.touchBeginEvent.connect(touchBegin);
Controller.touchEndEvent.connect(touchEnd);
Script.scriptEnding.connect(function () {
ending();
});
}
module.exports = {
init: init,
ending: ending
}
}()); // END LOCAL_SCOPE

View file

@ -29,6 +29,7 @@ var logEnabled = false;
var radar = Script.require('./radar.js');
var uniqueColor = Script.require('./uniqueColor.js');
var displayNames = Script.require('./displayNames.js');
var clickWeb = Script.require('./clickWeb.js');
function printd(str) {
if (logEnabled) {
@ -97,9 +98,11 @@ function switchToMode(newMode) {
if (currentMode == MODE_RADAR) {
radar.startRadarMode();
displayNames.ending();
clickWeb.ending();
} else if (currentMode == MODE_MY_VIEW) {
// nothing to do yet
displayNames.init();
clickWeb.init();
} else {
printd("Unknown view mode " + currentMode);
}