mirror of
https://github.com/lubosz/overte.git
synced 2025-08-08 03:48:38 +02:00
Merge pull request #8968 from ctrlaltdavid/21089
Clara.io marketplace improvements
This commit is contained in:
commit
61c60fca2d
40 changed files with 2094 additions and 513 deletions
|
@ -17,7 +17,10 @@
|
||||||
var KEYBOARD_HEIGHT = 200;
|
var KEYBOARD_HEIGHT = 200;
|
||||||
|
|
||||||
function shouldRaiseKeyboard() {
|
function shouldRaiseKeyboard() {
|
||||||
if (document.activeElement.nodeName === "INPUT" || document.activeElement.nodeName === "TEXTAREA") {
|
var nodeName = document.activeElement.nodeName;
|
||||||
|
var nodeType = document.activeElement.type;
|
||||||
|
if (nodeName === "INPUT" && (nodeType === "text" || nodeType === "number" || nodeType === "password")
|
||||||
|
|| document.activeElement.nodeName === "TEXTAREA") {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// check for contenteditable attribute
|
// check for contenteditable attribute
|
||||||
|
|
|
@ -23,6 +23,8 @@ ScrollingWindow {
|
||||||
|
|
||||||
property alias eventBridge: eventBridgeWrapper.eventBridge
|
property alias eventBridge: eventBridgeWrapper.eventBridge
|
||||||
|
|
||||||
|
signal loadingChanged(int status)
|
||||||
|
|
||||||
x: 100
|
x: 100
|
||||||
y: 100
|
y: 100
|
||||||
|
|
||||||
|
@ -44,6 +46,10 @@ ScrollingWindow {
|
||||||
hidePermissionsBar();
|
hidePermissionsBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAutoAdd(auto) {
|
||||||
|
desktop.setAutoAdd(auto);
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id:item
|
id:item
|
||||||
width: pane.contentWidth
|
width: pane.contentWidth
|
||||||
|
@ -197,7 +203,7 @@ ScrollingWindow {
|
||||||
|
|
||||||
WebView {
|
WebView {
|
||||||
id: webview
|
id: webview
|
||||||
url: "https://highfidelity.com"
|
url: "https://highfidelity.com/"
|
||||||
|
|
||||||
property alias eventBridgeWrapper: eventBridgeWrapper
|
property alias eventBridgeWrapper: eventBridgeWrapper
|
||||||
|
|
||||||
|
@ -243,6 +249,7 @@ ScrollingWindow {
|
||||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||||
addressBar.text = loadRequest.url
|
addressBar.text = loadRequest.url
|
||||||
}
|
}
|
||||||
|
root.loadingChanged(loadRequest.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
onIconChanged: {
|
onIconChanged: {
|
||||||
|
@ -254,7 +261,7 @@ ScrollingWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
desktop.initWebviewProfileHandlers(webview.profile)
|
desktop.initWebviewProfileHandlers(webview.profile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,167 +0,0 @@
|
||||||
//
|
|
||||||
// Marketplaces.qml
|
|
||||||
//
|
|
||||||
// Created by Elisa Lupin-Jimenez on 3 Aug 2016
|
|
||||||
// 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 QtQuick.Controls 1.4
|
|
||||||
import QtWebChannel 1.0
|
|
||||||
import QtWebEngine 1.1
|
|
||||||
import QtWebSockets 1.0
|
|
||||||
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
|
|
||||||
|
|
||||||
import "controls"
|
|
||||||
import "controls-uit" as Controls
|
|
||||||
import "styles"
|
|
||||||
import "styles-uit"
|
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
HifiConstants { id: hifi }
|
|
||||||
id: marketplace
|
|
||||||
anchors.fill: parent
|
|
||||||
property var marketplacesUrl: "../../scripts/system/html/marketplaces.html"
|
|
||||||
property int statusBarHeight: 50
|
|
||||||
property int statusMargin: 50
|
|
||||||
property string standardMessage: "Check out other marketplaces."
|
|
||||||
property string claraMessage: "Choose a model and click Download -> Autodesk FBX."
|
|
||||||
property string claraError: "High Fidelity only supports Autodesk FBX models."
|
|
||||||
|
|
||||||
Controls.BaseWebView {
|
|
||||||
id: webview
|
|
||||||
url: marketplacesUrl
|
|
||||||
anchors.top: marketplace.top
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - statusBarHeight
|
|
||||||
focus: true
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: zipTimer
|
|
||||||
running: false
|
|
||||||
repeat: false
|
|
||||||
interval: 1500
|
|
||||||
property var handler;
|
|
||||||
onTriggered: handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: alertTimer
|
|
||||||
running: false
|
|
||||||
repeat: false
|
|
||||||
interval: 9000
|
|
||||||
property var handler;
|
|
||||||
onTriggered: handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
property var autoCancel: 'var element = $("a.btn.cancel");
|
|
||||||
element.click();'
|
|
||||||
|
|
||||||
property var simpleDownload: 'var element = $("a.download-file");
|
|
||||||
element.removeClass("download-file");
|
|
||||||
element.removeAttr("download");'
|
|
||||||
|
|
||||||
function displayErrorStatus() {
|
|
||||||
alertTimer.handler = function() {
|
|
||||||
statusLabel.text = claraMessage;
|
|
||||||
statusBar.color = hifi.colors.blueHighlight;
|
|
||||||
statusIcon.text = hifi.glyphs.info;
|
|
||||||
}
|
|
||||||
alertTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
property var notFbxHandler: 'var element = $("a.btn.btn-primary.viewer-button.download-file")
|
|
||||||
element.click();'
|
|
||||||
|
|
||||||
// this code is for removing other file types from Clara.io's download options
|
|
||||||
//property var checkFileType: "$('[data-extension]:not([data-extension=\"fbx\"])').parent().remove()"
|
|
||||||
|
|
||||||
onLinkHovered: {
|
|
||||||
desktop.currentUrl = hoveredUrl;
|
|
||||||
//runJavaScript(checkFileType, function(){console.log("Remove filetypes JS injection");});
|
|
||||||
if (File.isZippedFbx(desktop.currentUrl)) {
|
|
||||||
runJavaScript(simpleDownload, function(){console.log("Download JS injection");});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.isZipped(desktop.currentUrl)) {
|
|
||||||
statusLabel.text = claraError;
|
|
||||||
statusBar.color = hifi.colors.redHighlight;
|
|
||||||
statusIcon.text = hifi.glyphs.alert;
|
|
||||||
runJavaScript(notFbxHandler, displayErrorStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadingChanged: {
|
|
||||||
if (File.isClaraLink(webview.url)) {
|
|
||||||
statusLabel.text = claraMessage;
|
|
||||||
} else {
|
|
||||||
statusLabel.text = standardMessage;
|
|
||||||
}
|
|
||||||
statusBar.color = hifi.colors.blueHighlight;
|
|
||||||
statusIcon.text = hifi.glyphs.info;
|
|
||||||
}
|
|
||||||
|
|
||||||
onNewViewRequested: {
|
|
||||||
var component = Qt.createComponent("Browser.qml");
|
|
||||||
var newWindow = component.createObject(desktop);
|
|
||||||
request.openIn(newWindow.webView);
|
|
||||||
if (File.isZippedFbx(desktop.currentUrl)) {
|
|
||||||
runJavaScript(autoCancel);
|
|
||||||
zipTimer.handler = function() {
|
|
||||||
newWindow.destroy();
|
|
||||||
}
|
|
||||||
zipTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: statusBar
|
|
||||||
anchors.top: webview.bottom
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
color: hifi.colors.blueHighlight
|
|
||||||
|
|
||||||
Controls.Button {
|
|
||||||
id: switchMarketView
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: statusMargin
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 150
|
|
||||||
text: "See all markets"
|
|
||||||
onClicked: {
|
|
||||||
webview.url = "../../scripts/system/html/marketplaces.html";
|
|
||||||
statusLabel.text = standardMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Controls.Label {
|
|
||||||
id: statusLabel
|
|
||||||
anchors.verticalCenter: switchMarketView.verticalCenter
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: statusMargin
|
|
||||||
color: hifi.colors.white
|
|
||||||
text: standardMessage
|
|
||||||
size: 18
|
|
||||||
}
|
|
||||||
|
|
||||||
HiFiGlyphs {
|
|
||||||
id: statusIcon
|
|
||||||
anchors.right: statusLabel.left
|
|
||||||
anchors.verticalCenter: statusLabel.verticalCenter
|
|
||||||
text: hifi.glyphs.info
|
|
||||||
color: hifi.colors.white
|
|
||||||
size: hifi.fontSizes.tableHeadingIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -27,6 +27,7 @@ Windows.ScrollingWindow {
|
||||||
destroyOnCloseButton: false
|
destroyOnCloseButton: false
|
||||||
property alias source: webview.url
|
property alias source: webview.url
|
||||||
property alias eventBridge: eventBridgeWrapper.eventBridge;
|
property alias eventBridge: eventBridgeWrapper.eventBridge;
|
||||||
|
property alias scriptUrl: webview.userScriptUrl
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: eventBridgeWrapper
|
id: eventBridgeWrapper
|
||||||
|
@ -71,6 +72,8 @@ Windows.ScrollingWindow {
|
||||||
focus: true
|
focus: true
|
||||||
webChannel.registeredObjects: [eventBridgeWrapper]
|
webChannel.registeredObjects: [eventBridgeWrapper]
|
||||||
|
|
||||||
|
property string userScriptUrl: ""
|
||||||
|
|
||||||
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
||||||
WebEngineScript {
|
WebEngineScript {
|
||||||
id: createGlobalEventBridge
|
id: createGlobalEventBridge
|
||||||
|
@ -87,7 +90,25 @@ Windows.ScrollingWindow {
|
||||||
worldId: WebEngineScript.MainWorld
|
worldId: WebEngineScript.MainWorld
|
||||||
}
|
}
|
||||||
|
|
||||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
|
// User script.
|
||||||
|
WebEngineScript {
|
||||||
|
id: userScript
|
||||||
|
sourceUrl: webview.userScriptUrl
|
||||||
|
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||||
|
|
||||||
|
function onWebEventReceived(event) {
|
||||||
|
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
||||||
|
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
eventBridge.webEventReceived.connect(onWebEventReceived);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
import QtQuick 2.3
|
import QtQuick 2.3
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtWebChannel 1.0
|
import QtWebChannel 1.0
|
||||||
import QtWebEngine 1.1
|
import QtWebEngine 1.2
|
||||||
import QtWebSockets 1.0
|
import QtWebSockets 1.0
|
||||||
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
|
|
||||||
|
|
||||||
import "windows" as Windows
|
import "windows" as Windows
|
||||||
import "controls"
|
import "controls"
|
||||||
|
@ -23,10 +22,22 @@ Windows.Window {
|
||||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||||
destroyOnCloseButton: false
|
destroyOnCloseButton: false
|
||||||
property var source;
|
property var source;
|
||||||
property var eventBridge;
|
|
||||||
property var component;
|
property var component;
|
||||||
property var dynamicContent;
|
property var dynamicContent;
|
||||||
|
|
||||||
|
// Keyboard control properties in case needed by QML content.
|
||||||
|
property bool keyboardEnabled: false
|
||||||
|
property bool keyboardRaised: false
|
||||||
|
property bool punctuationMode: false
|
||||||
|
|
||||||
|
// JavaScript event bridge object in case QML content includes Web content.
|
||||||
|
property alias eventBridge: eventBridgeWrapper.eventBridge;
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: eventBridgeWrapper
|
||||||
|
WebChannel.id: "eventBridgeWrapper"
|
||||||
|
property var eventBridge;
|
||||||
|
}
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
if (dynamicContent) {
|
if (dynamicContent) {
|
||||||
|
@ -72,7 +83,6 @@ Windows.Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentHolder
|
id: contentHolder
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
27
interface/resources/qml/Web3DOverlay.qml
Normal file
27
interface/resources/qml/Web3DOverlay.qml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//
|
||||||
|
// Web3DOverlay.qml
|
||||||
|
//
|
||||||
|
// Created by David Rowe on 16 Dec 2016.
|
||||||
|
// 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 QtQuick.Controls 1.4
|
||||||
|
|
||||||
|
import "controls" as Controls
|
||||||
|
|
||||||
|
Controls.WebView {
|
||||||
|
|
||||||
|
function onWebEventReceived(event) {
|
||||||
|
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
||||||
|
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
eventBridge.webEventReceived.connect(onWebEventReceived);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import HFWebEngineProfile 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property alias url: root.url
|
property alias url: root.url
|
||||||
|
property alias scriptURL: root.userScriptUrl
|
||||||
property alias eventBridge: eventBridgeWrapper.eventBridge
|
property alias eventBridge: eventBridgeWrapper.eventBridge
|
||||||
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
|
property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false
|
||||||
property bool keyboardRaised: false
|
property bool keyboardRaised: false
|
||||||
|
@ -38,6 +39,8 @@ Item {
|
||||||
storageName: "qmlWebEngine"
|
storageName: "qmlWebEngine"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property string userScriptUrl: ""
|
||||||
|
|
||||||
// creates a global EventBridge object.
|
// creates a global EventBridge object.
|
||||||
WebEngineScript {
|
WebEngineScript {
|
||||||
id: createGlobalEventBridge
|
id: createGlobalEventBridge
|
||||||
|
@ -54,7 +57,15 @@ Item {
|
||||||
worldId: WebEngineScript.MainWorld
|
worldId: WebEngineScript.MainWorld
|
||||||
}
|
}
|
||||||
|
|
||||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
|
// User script.
|
||||||
|
WebEngineScript {
|
||||||
|
id: userScript
|
||||||
|
sourceUrl: root.userScriptUrl
|
||||||
|
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||||
|
|
||||||
property string newUrl: ""
|
property string newUrl: ""
|
||||||
|
|
||||||
|
|
|
@ -100,21 +100,25 @@ OriginalDesktop.Desktop {
|
||||||
// Accept a download through the webview
|
// Accept a download through the webview
|
||||||
property bool webViewProfileSetup: false
|
property bool webViewProfileSetup: false
|
||||||
property string currentUrl: ""
|
property string currentUrl: ""
|
||||||
|
property string downloadUrl: ""
|
||||||
property string adaptedPath: ""
|
property string adaptedPath: ""
|
||||||
property string tempDir: ""
|
property string tempDir: ""
|
||||||
|
property bool autoAdd: false
|
||||||
|
|
||||||
function initWebviewProfileHandlers(profile) {
|
function initWebviewProfileHandlers(profile) {
|
||||||
console.log("The webview url in desktop is: " + currentUrl);
|
console.log("The webview url in desktop is: " + currentUrl);
|
||||||
|
downloadUrl = currentUrl;
|
||||||
if (webViewProfileSetup) return;
|
if (webViewProfileSetup) return;
|
||||||
webViewProfileSetup = true;
|
webViewProfileSetup = true;
|
||||||
|
|
||||||
profile.downloadRequested.connect(function(download){
|
profile.downloadRequested.connect(function(download){
|
||||||
console.log("Download start: " + download.state);
|
console.log("Download start: " + download.state);
|
||||||
adaptedPath = File.convertUrlToPath(currentUrl);
|
adaptedPath = File.convertUrlToPath(downloadUrl);
|
||||||
tempDir = File.getTempDir();
|
tempDir = File.getTempDir();
|
||||||
console.log("Temp dir created: " + tempDir);
|
console.log("Temp dir created: " + tempDir);
|
||||||
download.path = tempDir + "/" + adaptedPath;
|
download.path = tempDir + "/" + adaptedPath;
|
||||||
console.log("Path where object should download: " + download.path);
|
console.log("Path where object should download: " + download.path);
|
||||||
|
console.log("Auto add: " + autoAdd);
|
||||||
download.accept();
|
download.accept();
|
||||||
if (download.state === WebEngineDownloadItem.DownloadInterrupted) {
|
if (download.state === WebEngineDownloadItem.DownloadInterrupted) {
|
||||||
console.log("download failed to complete");
|
console.log("download failed to complete");
|
||||||
|
@ -123,13 +127,18 @@ OriginalDesktop.Desktop {
|
||||||
|
|
||||||
profile.downloadFinished.connect(function(download){
|
profile.downloadFinished.connect(function(download){
|
||||||
if (download.state === WebEngineDownloadItem.DownloadCompleted) {
|
if (download.state === WebEngineDownloadItem.DownloadCompleted) {
|
||||||
File.runUnzip(download.path, currentUrl);
|
File.runUnzip(download.path, downloadUrl, autoAdd);
|
||||||
} else {
|
} else {
|
||||||
console.log("The download was corrupted, state: " + download.state);
|
console.log("The download was corrupted, state: " + download.state);
|
||||||
}
|
}
|
||||||
|
autoAdd = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAutoAdd(auto) {
|
||||||
|
autoAdd = auto;
|
||||||
|
}
|
||||||
|
|
||||||
// Create or fetch a toolbar with the given name
|
// Create or fetch a toolbar with the given name
|
||||||
function getToolbar(name) {
|
function getToolbar(name) {
|
||||||
var result = toolbars[name];
|
var result = toolbars[name];
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include <QtMultimedia/QMediaPlayer>
|
#include <QtMultimedia/QMediaPlayer>
|
||||||
|
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
|
||||||
#include <gl/QOpenGLContextWrapper.h>
|
#include <gl/QOpenGLContextWrapper.h>
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
#include <AnimDebugDraw.h>
|
#include <AnimDebugDraw.h>
|
||||||
#include <BuildInfo.h>
|
#include <BuildInfo.h>
|
||||||
#include <AssetClient.h>
|
#include <AssetClient.h>
|
||||||
|
#include <AssetUpload.h>
|
||||||
#include <AutoUpdater.h>
|
#include <AutoUpdater.h>
|
||||||
#include <AudioInjectorManager.h>
|
#include <AudioInjectorManager.h>
|
||||||
#include <CursorManager.h>
|
#include <CursorManager.h>
|
||||||
|
@ -79,6 +81,7 @@
|
||||||
#include <UserActivityLoggerScriptingInterface.h>
|
#include <UserActivityLoggerScriptingInterface.h>
|
||||||
#include <LogHandler.h>
|
#include <LogHandler.h>
|
||||||
#include <MainWindow.h>
|
#include <MainWindow.h>
|
||||||
|
#include <MappingRequest.h>
|
||||||
#include <MessagesClient.h>
|
#include <MessagesClient.h>
|
||||||
#include <ModelEntityItem.h>
|
#include <ModelEntityItem.h>
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
|
@ -157,6 +160,7 @@
|
||||||
#include "ui/DialogsManager.h"
|
#include "ui/DialogsManager.h"
|
||||||
#include "ui/LoginDialog.h"
|
#include "ui/LoginDialog.h"
|
||||||
#include "ui/overlays/Cube3DOverlay.h"
|
#include "ui/overlays/Cube3DOverlay.h"
|
||||||
|
#include "ui/overlays/Web3DOverlay.h"
|
||||||
#include "ui/Snapshot.h"
|
#include "ui/Snapshot.h"
|
||||||
#include "ui/SnapshotAnimated.h"
|
#include "ui/SnapshotAnimated.h"
|
||||||
#include "ui/StandAloneJSConsole.h"
|
#include "ui/StandAloneJSConsole.h"
|
||||||
|
@ -1190,11 +1194,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
||||||
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
|
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
|
||||||
|
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||||
setKeyboardFocusEntity(entityItemID);
|
setKeyboardFocusEntity(entityItemID);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) {
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) {
|
||||||
if (entityItemID == _keyboardFocusedItem.get()) {
|
if (entityItemID == _keyboardFocusedEntity.get()) {
|
||||||
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1204,7 +1209,26 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keyboard focus handling for Web overlays.
|
||||||
|
auto overlays = &(qApp->getOverlays());
|
||||||
|
|
||||||
|
connect(overlays, &Overlays::mousePressOnOverlay, [=](unsigned int overlayID, const PointerEvent& event) {
|
||||||
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
|
setKeyboardFocusOverlay(overlayID);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(overlays, &Overlays::overlayDeleted, [=](unsigned int overlayID) {
|
||||||
|
if (overlayID == _keyboardFocusedOverlay.get()) {
|
||||||
|
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(overlays, &Overlays::mousePressOffOverlay, [=]() {
|
||||||
|
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||||
|
});
|
||||||
|
|
||||||
connect(this, &Application::aboutToQuit, [=]() {
|
connect(this, &Application::aboutToQuit, [=]() {
|
||||||
|
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||||
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1476,6 +1500,24 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
// After all of the constructor is completed, then set firstRun to false.
|
// After all of the constructor is completed, then set firstRun to false.
|
||||||
firstRun.set(false);
|
firstRun.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
|
||||||
|
static const int ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS = 1000;
|
||||||
|
_addAssetToWorldResizeTimer.setInterval(ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS);
|
||||||
|
connect(&_addAssetToWorldResizeTimer, &QTimer::timeout, this, &Application::addAssetToWorldCheckModelSize);
|
||||||
|
|
||||||
|
// Auto-update and close adding asset to world info message box.
|
||||||
|
static const int ADD_ASSET_TO_WORLD_INFO_TIMEOUT_MS = 5000;
|
||||||
|
_addAssetToWorldInfoTimer.setInterval(ADD_ASSET_TO_WORLD_INFO_TIMEOUT_MS);
|
||||||
|
_addAssetToWorldInfoTimer.setSingleShot(true);
|
||||||
|
connect(&_addAssetToWorldInfoTimer, &QTimer::timeout, this, &Application::addAssetToWorldInfoTimeout);
|
||||||
|
static const int ADD_ASSET_TO_WORLD_ERROR_TIMEOUT_MS = 8000;
|
||||||
|
_addAssetToWorldErrorTimer.setInterval(ADD_ASSET_TO_WORLD_ERROR_TIMEOUT_MS);
|
||||||
|
_addAssetToWorldErrorTimer.setSingleShot(true);
|
||||||
|
connect(&_addAssetToWorldErrorTimer, &QTimer::timeout, this, &Application::addAssetToWorldErrorTimeout);
|
||||||
|
|
||||||
|
connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose);
|
||||||
|
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
|
||||||
|
@ -1865,9 +1907,9 @@ void Application::initializeUi() {
|
||||||
rootContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
rootContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||||
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||||
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
||||||
FileScriptingInterface* fileDownload = new FileScriptingInterface(engine);
|
_fileDownload = new FileScriptingInterface(engine);
|
||||||
rootContext->setContextProperty("File", fileDownload);
|
rootContext->setContextProperty("File", _fileDownload);
|
||||||
connect(fileDownload, &FileScriptingInterface::unzipSuccess, this, &Application::showAssetServerWidget);
|
connect(_fileDownload, &FileScriptingInterface::unzipResult, this, &Application::handleUnzip);
|
||||||
rootContext->setContextProperty("MyAvatar", getMyAvatar().get());
|
rootContext->setContextProperty("MyAvatar", getMyAvatar().get());
|
||||||
rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
|
rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
|
||||||
rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
|
rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
|
||||||
|
@ -2395,26 +2437,49 @@ bool Application::event(QEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
if (!_keyboardFocusedItem.get().isInvalidID()) {
|
if (!_keyboardFocusedEntity.get().isInvalidID()) {
|
||||||
switch (event->type()) {
|
switch (event->type()) {
|
||||||
case QEvent::KeyPress:
|
case QEvent::KeyPress:
|
||||||
case QEvent::KeyRelease: {
|
case QEvent::KeyRelease: {
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
//auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedItem.get());
|
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedEntity.get());
|
||||||
if (entity && entity->getEventHandler()) {
|
if (entity && entity->getEventHandler()) {
|
||||||
event->setAccepted(false);
|
event->setAccepted(false);
|
||||||
QCoreApplication::sendEvent(entity->getEventHandler(), event);
|
QCoreApplication::sendEvent(entity->getEventHandler(), event);
|
||||||
if (event->isAccepted()) {
|
if (event->isAccepted()) {
|
||||||
_lastAcceptedKeyPress = usecTimestampNow();
|
_lastAcceptedKeyPress = usecTimestampNow();
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
default:
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
{
|
||||||
break;
|
if (_keyboardFocusedOverlay.get() != UNKNOWN_OVERLAY_ID) {
|
||||||
}
|
switch (event->type()) {
|
||||||
|
case QEvent::KeyPress:
|
||||||
|
case QEvent::KeyRelease: {
|
||||||
|
// Only Web overlays can have focus.
|
||||||
|
auto overlay =
|
||||||
|
std::dynamic_pointer_cast<Web3DOverlay>(getOverlays().getOverlay(_keyboardFocusedOverlay.get()));
|
||||||
|
if (overlay && overlay->getEventHandler()) {
|
||||||
|
event->setAccepted(false);
|
||||||
|
QCoreApplication::sendEvent(overlay->getEventHandler(), event);
|
||||||
|
if (event->isAccepted()) {
|
||||||
|
_lastAcceptedKeyPress = usecTimestampNow();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2974,6 +3039,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
||||||
|
|
||||||
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
|
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
|
||||||
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y()))) {
|
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y()))) {
|
||||||
|
getOverlays().mouseMoveEvent(&mappedEvent);
|
||||||
getEntities()->mouseMoveEvent(&mappedEvent);
|
getEntities()->mouseMoveEvent(&mappedEvent);
|
||||||
}
|
}
|
||||||
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
_controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts
|
||||||
|
@ -2986,7 +3052,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
_keyboardMouseDevice->mouseMoveEvent(event);
|
_keyboardMouseDevice->mouseMoveEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::mousePressEvent(QMouseEvent* event) {
|
void Application::mousePressEvent(QMouseEvent* event) {
|
||||||
|
@ -3008,6 +3073,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
||||||
event->buttons(), event->modifiers());
|
event->buttons(), event->modifiers());
|
||||||
|
|
||||||
if (!_aboutToQuit) {
|
if (!_aboutToQuit) {
|
||||||
|
getOverlays().mousePressEvent(&mappedEvent);
|
||||||
getEntities()->mousePressEvent(&mappedEvent);
|
getEntities()->mousePressEvent(&mappedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3018,7 +3084,6 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (hasFocus()) {
|
if (hasFocus()) {
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
_keyboardMouseDevice->mousePressEvent(event);
|
_keyboardMouseDevice->mousePressEvent(event);
|
||||||
|
@ -3029,7 +3094,6 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
||||||
HFActionEvent actionEvent(HFActionEvent::startType(),
|
HFActionEvent actionEvent(HFActionEvent::startType(),
|
||||||
computePickRay(mappedEvent.x(), mappedEvent.y()));
|
computePickRay(mappedEvent.x(), mappedEvent.y()));
|
||||||
sendEvent(this, &actionEvent);
|
sendEvent(this, &actionEvent);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3054,6 +3118,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
event->buttons(), event->modifiers());
|
event->buttons(), event->modifiers());
|
||||||
|
|
||||||
if (!_aboutToQuit) {
|
if (!_aboutToQuit) {
|
||||||
|
getOverlays().mouseReleaseEvent(&mappedEvent);
|
||||||
getEntities()->mouseReleaseEvent(&mappedEvent);
|
getEntities()->mouseReleaseEvent(&mappedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3293,20 +3358,30 @@ void Application::idle(float nsecsElapsed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds
|
// Update focus highlight for entity or overlay.
|
||||||
{
|
{
|
||||||
if (!_keyboardFocusedItem.get().isInvalidID()) {
|
if (!_keyboardFocusedEntity.get().isInvalidID() || _keyboardFocusedOverlay.get() != UNKNOWN_OVERLAY_ID) {
|
||||||
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
|
const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus
|
||||||
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
|
quint64 elapsedSinceAcceptedKeyPress = usecTimestampNow() - _lastAcceptedKeyPress;
|
||||||
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
|
if (elapsedSinceAcceptedKeyPress > LOSE_FOCUS_AFTER_ELAPSED_TIME) {
|
||||||
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
||||||
|
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||||
} else {
|
} else {
|
||||||
// update position of highlight overlay
|
// update position of highlight overlay
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
if (!_keyboardFocusedEntity.get().isInvalidID()) {
|
||||||
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedItem.get());
|
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedEntity.get());
|
||||||
if (entity && _keyboardFocusHighlight) {
|
if (entity && _keyboardFocusHighlight) {
|
||||||
_keyboardFocusHighlight->setRotation(entity->getRotation());
|
_keyboardFocusHighlight->setRotation(entity->getRotation());
|
||||||
_keyboardFocusHighlight->setPosition(entity->getPosition());
|
_keyboardFocusHighlight->setPosition(entity->getPosition());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only Web overlays can have focus.
|
||||||
|
auto overlay =
|
||||||
|
std::dynamic_pointer_cast<Web3DOverlay>(getOverlays().getOverlay(_keyboardFocusedOverlay.get()));
|
||||||
|
if (overlay && _keyboardFocusHighlight) {
|
||||||
|
_keyboardFocusHighlight->setRotation(overlay->getRotation());
|
||||||
|
_keyboardFocusHighlight->setPosition(overlay->getPosition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3894,8 +3969,31 @@ void Application::rotationModeChanged() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions) {
|
||||||
|
// Create focus
|
||||||
|
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
|
||||||
|
_keyboardFocusHighlight = std::make_shared<Cube3DOverlay>();
|
||||||
|
_keyboardFocusHighlight->setAlpha(1.0f);
|
||||||
|
_keyboardFocusHighlight->setBorderSize(1.0f);
|
||||||
|
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
|
||||||
|
_keyboardFocusHighlight->setIsSolid(false);
|
||||||
|
_keyboardFocusHighlight->setPulseMin(0.5);
|
||||||
|
_keyboardFocusHighlight->setPulseMax(1.0);
|
||||||
|
_keyboardFocusHighlight->setColorPulse(1.0);
|
||||||
|
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
|
||||||
|
_keyboardFocusHighlight->setDrawInFront(false);
|
||||||
|
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position focus
|
||||||
|
_keyboardFocusHighlight->setRotation(rotation);
|
||||||
|
_keyboardFocusHighlight->setPosition(position);
|
||||||
|
_keyboardFocusHighlight->setDimensions(dimensions);
|
||||||
|
_keyboardFocusHighlight->setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
QUuid Application::getKeyboardFocusEntity() const {
|
QUuid Application::getKeyboardFocusEntity() const {
|
||||||
return _keyboardFocusedItem.get();
|
return _keyboardFocusedEntity.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::setKeyboardFocusEntity(QUuid id) {
|
void Application::setKeyboardFocusEntity(QUuid id) {
|
||||||
|
@ -3903,21 +4001,21 @@ void Application::setKeyboardFocusEntity(QUuid id) {
|
||||||
setKeyboardFocusEntity(entityItemID);
|
setKeyboardFocusEntity(entityItemID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f;
|
||||||
|
|
||||||
void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
|
void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
if (_keyboardFocusedEntity.get() != entityItemID) {
|
||||||
if (_keyboardFocusedItem.get() != entityItemID) {
|
_keyboardFocusedEntity.set(entityItemID);
|
||||||
// reset focused entity
|
|
||||||
_keyboardFocusedItem.set(UNKNOWN_ENTITY_ID);
|
if (_keyboardFocusHighlight && _keyboardFocusedOverlay.get() == UNKNOWN_OVERLAY_ID) {
|
||||||
if (_keyboardFocusHighlight) {
|
|
||||||
_keyboardFocusHighlight->setVisible(false);
|
_keyboardFocusHighlight->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if invalid, return without expensive (locking) operations
|
|
||||||
if (entityItemID == UNKNOWN_ENTITY_ID) {
|
if (entityItemID == UNKNOWN_ENTITY_ID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if valid, query properties
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
|
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
|
||||||
if (!properties.getLocked() && properties.getVisible()) {
|
if (!properties.getLocked() && properties.getVisible()) {
|
||||||
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
|
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
|
||||||
|
@ -3926,34 +4024,49 @@ void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
|
||||||
if (_keyboardMouseDevice->isActive()) {
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
_keyboardMouseDevice->pluginFocusOutEvent();
|
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||||
}
|
}
|
||||||
_keyboardFocusedItem.set(entityItemID);
|
|
||||||
_lastAcceptedKeyPress = usecTimestampNow();
|
_lastAcceptedKeyPress = usecTimestampNow();
|
||||||
|
|
||||||
// create a focus
|
setKeyboardFocusHighlight(entity->getPosition(), entity->getRotation(),
|
||||||
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
|
entity->getDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR);
|
||||||
_keyboardFocusHighlight = std::make_shared<Cube3DOverlay>();
|
|
||||||
_keyboardFocusHighlight->setAlpha(1.0f);
|
|
||||||
_keyboardFocusHighlight->setBorderSize(1.0f);
|
|
||||||
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
|
|
||||||
_keyboardFocusHighlight->setIsSolid(false);
|
|
||||||
_keyboardFocusHighlight->setPulseMin(0.5);
|
|
||||||
_keyboardFocusHighlight->setPulseMax(1.0);
|
|
||||||
_keyboardFocusHighlight->setColorPulse(1.0);
|
|
||||||
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
|
|
||||||
_keyboardFocusHighlight->setDrawInFront(false);
|
|
||||||
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// position the focus
|
|
||||||
_keyboardFocusHighlight->setRotation(entity->getRotation());
|
|
||||||
_keyboardFocusHighlight->setPosition(entity->getPosition());
|
|
||||||
_keyboardFocusHighlight->setDimensions(entity->getDimensions() * 1.05f);
|
|
||||||
_keyboardFocusHighlight->setVisible(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int Application::getKeyboardFocusOverlay() {
|
||||||
|
return _keyboardFocusedOverlay.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::setKeyboardFocusOverlay(unsigned int overlayID) {
|
||||||
|
if (overlayID != _keyboardFocusedOverlay.get()) {
|
||||||
|
_keyboardFocusedOverlay.set(overlayID);
|
||||||
|
|
||||||
|
if (_keyboardFocusHighlight && _keyboardFocusedEntity.get() == UNKNOWN_ENTITY_ID) {
|
||||||
|
_keyboardFocusHighlight->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlayID == UNKNOWN_OVERLAY_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto overlayType = getOverlays().getOverlayType(overlayID);
|
||||||
|
auto isVisible = getOverlays().getProperty(overlayID, "visible").value.toBool();
|
||||||
|
if (overlayType == Web3DOverlay::TYPE && isVisible) {
|
||||||
|
auto overlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlays().getOverlay(overlayID));
|
||||||
|
overlay->setProxyWindow(_window->windowHandle());
|
||||||
|
|
||||||
|
if (_keyboardMouseDevice->isActive()) {
|
||||||
|
_keyboardMouseDevice->pluginFocusOutEvent();
|
||||||
|
}
|
||||||
|
_lastAcceptedKeyPress = usecTimestampNow();
|
||||||
|
|
||||||
|
auto size = overlay->getSize() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR;
|
||||||
|
const float OVERLAY_DEPTH = 0.0105f;
|
||||||
|
setKeyboardFocusHighlight(overlay->getPosition(), overlay->getRotation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::updateDialogs(float deltaTime) const {
|
void Application::updateDialogs(float deltaTime) const {
|
||||||
PerformanceTimer perfTimer("updateDialogs");
|
PerformanceTimer perfTimer("updateDialogs");
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
|
@ -5512,6 +5625,464 @@ void Application::showAssetServerWidget(QString filePath) {
|
||||||
startUpload(nullptr, nullptr);
|
startUpload(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldFromURL(QString url) {
|
||||||
|
qInfo(interfaceapp) << "Download asset and add to world from" << url;
|
||||||
|
|
||||||
|
QString filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
|
||||||
|
|
||||||
|
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
|
||||||
|
QString errorInfo = "You do not have permissions to write to the Asset Server.";
|
||||||
|
qWarning(interfaceapp) << "Error downloading asset: " + errorInfo;
|
||||||
|
addAssetToWorldError(filename, errorInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAssetToWorldInfo(filename, "Downloading asset file " + filename + ".");
|
||||||
|
|
||||||
|
auto request = ResourceManager::createResourceRequest(nullptr, QUrl(url));
|
||||||
|
connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished);
|
||||||
|
request->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldFromURLRequestFinished() {
|
||||||
|
auto request = qobject_cast<ResourceRequest*>(sender());
|
||||||
|
auto url = request->getUrl().toString();
|
||||||
|
auto result = request->getResult();
|
||||||
|
|
||||||
|
QString filename = url.section("filename=", 1, 1); // Filename from trailing "?filename=" URL parameter.
|
||||||
|
|
||||||
|
if (result == ResourceRequest::Success) {
|
||||||
|
qInfo(interfaceapp) << "Downloaded asset from" << url;
|
||||||
|
QTemporaryDir temporaryDir;
|
||||||
|
temporaryDir.setAutoRemove(false);
|
||||||
|
if (temporaryDir.isValid()) {
|
||||||
|
QString temporaryDirPath = temporaryDir.path();
|
||||||
|
QString downloadPath = temporaryDirPath + "/" + filename;
|
||||||
|
qInfo(interfaceapp) << "Download path:" << downloadPath;
|
||||||
|
|
||||||
|
QFile tempFile(downloadPath);
|
||||||
|
if (tempFile.open(QIODevice::WriteOnly)) {
|
||||||
|
tempFile.write(request->getData());
|
||||||
|
addAssetToWorldInfoClear(filename); // Remove message from list; next one added will have a different key.
|
||||||
|
qApp->getFileDownloadInterface()->runUnzip(downloadPath, url, true);
|
||||||
|
} else {
|
||||||
|
QString errorInfo = "Couldn't open temporary file for download";
|
||||||
|
qWarning(interfaceapp) << errorInfo;
|
||||||
|
addAssetToWorldError(filename, errorInfo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QString errorInfo = "Couldn't create temporary directory for download";
|
||||||
|
qWarning(interfaceapp) << errorInfo;
|
||||||
|
addAssetToWorldError(filename, errorInfo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning(interfaceapp) << "Error downloading" << url << ":" << request->getResultString();
|
||||||
|
addAssetToWorldError(filename, "Error downloading " + filename + " : " + request->getResultString());
|
||||||
|
}
|
||||||
|
|
||||||
|
request->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString filenameFromPath(QString filePath) {
|
||||||
|
return filePath.right(filePath.length() - filePath.lastIndexOf("/") - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldUnzipFailure(QString filePath) {
|
||||||
|
QString filename = filenameFromPath(QUrl(filePath).toLocalFile());
|
||||||
|
qWarning(interfaceapp) << "Couldn't unzip file" << filePath;
|
||||||
|
addAssetToWorldError(filename, "Couldn't unzip file " + filename + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorld(QString filePath) {
|
||||||
|
// Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget().
|
||||||
|
|
||||||
|
QString path = QUrl(filePath).toLocalFile();
|
||||||
|
QString filename = filenameFromPath(path);
|
||||||
|
QString mapping = "/" + filename;
|
||||||
|
|
||||||
|
// Test repeated because possibly different code paths.
|
||||||
|
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
|
||||||
|
QString errorInfo = "You do not have permissions to write to the Asset Server.";
|
||||||
|
qWarning(interfaceapp) << "Error downloading asset: " + errorInfo;
|
||||||
|
addAssetToWorldError(filename, errorInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addAssetToWorldInfo(filename, "Adding " + mapping.mid(1) + " to the Asset Server.");
|
||||||
|
|
||||||
|
addAssetToWorldWithNewMapping(path, mapping, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) {
|
||||||
|
auto request = DependencyManager::get<AssetClient>()->createGetMappingRequest(mapping);
|
||||||
|
|
||||||
|
QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable {
|
||||||
|
const int MAX_COPY_COUNT = 100; // Limit number of duplicate assets; recursion guard.
|
||||||
|
auto result = request->getError();
|
||||||
|
if (result == GetMappingRequest::NotFound) {
|
||||||
|
addAssetToWorldUpload(filePath, mapping);
|
||||||
|
} else if (result != GetMappingRequest::NoError) {
|
||||||
|
QString errorInfo = "Could not map asset name: "
|
||||||
|
+ mapping.left(mapping.length() - QString::number(copy).length() - 1);
|
||||||
|
qWarning(interfaceapp) << "Error downloading asset: " + errorInfo;
|
||||||
|
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||||
|
} else if (copy < MAX_COPY_COUNT - 1) {
|
||||||
|
if (copy > 0) {
|
||||||
|
mapping = mapping.remove(mapping.lastIndexOf("-"), QString::number(copy).length() + 1);
|
||||||
|
}
|
||||||
|
copy++;
|
||||||
|
mapping = mapping.insert(mapping.lastIndexOf("."), "-" + QString::number(copy));
|
||||||
|
addAssetToWorldWithNewMapping(filePath, mapping, copy);
|
||||||
|
} else {
|
||||||
|
QString errorInfo = "Too many copies of asset name: "
|
||||||
|
+ mapping.left(mapping.length() - QString::number(copy).length() - 1);
|
||||||
|
qWarning(interfaceapp) << "Error downloading asset: " + errorInfo;
|
||||||
|
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||||
|
}
|
||||||
|
request->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
request->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldUpload(QString filePath, QString mapping) {
|
||||||
|
qInfo(interfaceapp) << "Uploading" << filePath << "to Asset Server as" << mapping;
|
||||||
|
auto upload = DependencyManager::get<AssetClient>()->createUpload(filePath);
|
||||||
|
QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable {
|
||||||
|
if (upload->getError() != AssetUpload::NoError) {
|
||||||
|
QString errorInfo = "Could not upload asset to the Asset Server.";
|
||||||
|
qWarning(interfaceapp) << "Error downloading asset: " + errorInfo;
|
||||||
|
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||||
|
} else {
|
||||||
|
addAssetToWorldSetMapping(filePath, mapping, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove temporary directory created by Clara.io market place download.
|
||||||
|
int index = filePath.lastIndexOf("/model_repo/");
|
||||||
|
if (index > 0) {
|
||||||
|
QString tempDir = filePath.left(index);
|
||||||
|
qCDebug(interfaceapp) << "Removing temporary directory at: " + tempDir;
|
||||||
|
QDir(tempDir).removeRecursively();
|
||||||
|
}
|
||||||
|
|
||||||
|
upload->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
upload->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash) {
|
||||||
|
auto request = DependencyManager::get<AssetClient>()->createSetMappingRequest(mapping, hash);
|
||||||
|
connect(request, &SetMappingRequest::finished, this, [=](SetMappingRequest* request) mutable {
|
||||||
|
if (request->getError() != SetMappingRequest::NoError) {
|
||||||
|
QString errorInfo = "Could not set asset mapping.";
|
||||||
|
qWarning(interfaceapp) << "Error downloading asset: " + errorInfo;
|
||||||
|
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||||
|
} else {
|
||||||
|
addAssetToWorldAddEntity(filePath, mapping);
|
||||||
|
}
|
||||||
|
request->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
request->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
|
||||||
|
EntityItemProperties properties;
|
||||||
|
properties.setType(EntityTypes::Model);
|
||||||
|
properties.setName(mapping.right(mapping.length() - 1));
|
||||||
|
properties.setModelURL("atp:" + mapping);
|
||||||
|
properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
||||||
|
properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar.
|
||||||
|
properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions.
|
||||||
|
properties.setPosition(getMyAvatar()->getPosition() + getMyAvatar()->getOrientation() * glm::vec3(0.0f, 0.0f, -2.0f));
|
||||||
|
properties.setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
|
||||||
|
auto entityID = DependencyManager::get<EntityScriptingInterface>()->addEntity(properties);
|
||||||
|
|
||||||
|
// Note: Model dimensions are not available here; model is scaled per FBX mesh in RenderableModelEntityItem::update() later
|
||||||
|
// on. But FBX dimensions may be in cm, so we monitor for the dimension change and rescale again if warranted.
|
||||||
|
|
||||||
|
if (entityID == QUuid()) {
|
||||||
|
QString errorInfo = "Could not add asset " + mapping + " to world.";
|
||||||
|
qWarning(interfaceapp) << "Could not add asset to world: " + errorInfo;
|
||||||
|
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||||
|
} else {
|
||||||
|
// Monitor when asset is rendered in world so that can resize if necessary.
|
||||||
|
_addAssetToWorldResizeList.insert(entityID, 0); // List value is count of checks performed.
|
||||||
|
if (!_addAssetToWorldResizeTimer.isActive()) {
|
||||||
|
_addAssetToWorldResizeTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close progress message box.
|
||||||
|
addAssetToWorldInfoDone(filenameFromPath(filePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldCheckModelSize() {
|
||||||
|
if (_addAssetToWorldResizeList.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto item = _addAssetToWorldResizeList.begin();
|
||||||
|
while (item != _addAssetToWorldResizeList.end()) {
|
||||||
|
auto entityID = item.key();
|
||||||
|
|
||||||
|
EntityPropertyFlags propertyFlags;
|
||||||
|
propertyFlags += PROP_NAME;
|
||||||
|
propertyFlags += PROP_DIMENSIONS;
|
||||||
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||||
|
auto properties = entityScriptingInterface->getEntityProperties(entityID, propertyFlags);
|
||||||
|
auto name = properties.getName();
|
||||||
|
auto dimensions = properties.getDimensions();
|
||||||
|
|
||||||
|
const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}";
|
||||||
|
bool doResize = false;
|
||||||
|
|
||||||
|
const glm::vec3 DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f);
|
||||||
|
if (dimensions != DEFAULT_DIMENSIONS) {
|
||||||
|
|
||||||
|
// Scale model so that its maximum is exactly specific size.
|
||||||
|
const float MAXIMUM_DIMENSION = 1.0f;
|
||||||
|
auto previousDimensions = dimensions;
|
||||||
|
auto scale = std::min(MAXIMUM_DIMENSION / dimensions.x, std::min(MAXIMUM_DIMENSION / dimensions.y,
|
||||||
|
MAXIMUM_DIMENSION / dimensions.z));
|
||||||
|
dimensions *= scale;
|
||||||
|
qInfo(interfaceapp) << "Asset" << name << "auto-resized from" << previousDimensions << " to " << dimensions;
|
||||||
|
doResize = true;
|
||||||
|
|
||||||
|
item = _addAssetToWorldResizeList.erase(item); // Finished with this entity; advance to next.
|
||||||
|
} else {
|
||||||
|
// Increment count of checks done.
|
||||||
|
_addAssetToWorldResizeList[entityID]++;
|
||||||
|
|
||||||
|
const int CHECK_MODEL_SIZE_MAX_CHECKS = 300;
|
||||||
|
if (_addAssetToWorldResizeList[entityID] > CHECK_MODEL_SIZE_MAX_CHECKS) {
|
||||||
|
// Have done enough checks; model was either the default size or something's gone wrong.
|
||||||
|
|
||||||
|
// Rescale all dimensions.
|
||||||
|
const glm::vec3 UNIT_DIMENSIONS = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||||
|
dimensions = UNIT_DIMENSIONS;
|
||||||
|
qInfo(interfaceapp) << "Asset" << name << "auto-resize timed out; resized to " << dimensions;
|
||||||
|
doResize = true;
|
||||||
|
|
||||||
|
item = _addAssetToWorldResizeList.erase(item); // Finished with this entity; advance to next.
|
||||||
|
} else {
|
||||||
|
// No action on this entity; advance to next.
|
||||||
|
++item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doResize) {
|
||||||
|
EntityItemProperties properties;
|
||||||
|
properties.setDimensions(dimensions);
|
||||||
|
properties.setVisible(true);
|
||||||
|
properties.setCollisionless(false);
|
||||||
|
properties.setUserData(GRABBABLE_USER_DATA);
|
||||||
|
properties.setLastEdited(usecTimestampNow());
|
||||||
|
entityScriptingInterface->editEntity(entityID, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop timer if nothing in list to check.
|
||||||
|
if (_addAssetToWorldResizeList.size() == 0) {
|
||||||
|
_addAssetToWorldResizeTimer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Application::addAssetToWorldInfo(QString modelName, QString infoText) {
|
||||||
|
// Displays the most recent info message, subject to being overridden by error messages.
|
||||||
|
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cancel info timer if running.
|
||||||
|
If list has an entry for modelName, delete it (just one).
|
||||||
|
Append modelName, infoText to list.
|
||||||
|
Display infoText in message box unless an error is being displayed (i.e., error timer is running).
|
||||||
|
Show message box if not already visible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_addAssetToWorldInfoTimer.stop();
|
||||||
|
|
||||||
|
addAssetToWorldInfoClear(modelName);
|
||||||
|
|
||||||
|
_addAssetToWorldInfoKeys.append(modelName);
|
||||||
|
_addAssetToWorldInfoMessages.append(infoText);
|
||||||
|
|
||||||
|
if (!_addAssetToWorldErrorTimer.isActive()) {
|
||||||
|
if (!_addAssetToWorldMessageBox) {
|
||||||
|
_addAssetToWorldMessageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION,
|
||||||
|
"Downloading Asset", "", QMessageBox::NoButton, QMessageBox::NoButton);
|
||||||
|
connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
_addAssetToWorldMessageBox->setProperty("text", "\n" + infoText);
|
||||||
|
_addAssetToWorldMessageBox->setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldInfoClear(QString modelName) {
|
||||||
|
// Clears modelName entry from message list without affecting message currently displayed.
|
||||||
|
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete entry for modelName from list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto index = _addAssetToWorldInfoKeys.indexOf(modelName);
|
||||||
|
if (index > -1) {
|
||||||
|
_addAssetToWorldInfoKeys.removeAt(index);
|
||||||
|
_addAssetToWorldInfoMessages.removeAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldInfoDone(QString modelName) {
|
||||||
|
// Continues to display this message if the latest for a few seconds, then deletes it and displays the next latest.
|
||||||
|
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete entry for modelName from list.
|
||||||
|
(Re)start the info timer to update message box. ... onAddAssetToWorldInfoTimeout()
|
||||||
|
*/
|
||||||
|
|
||||||
|
addAssetToWorldInfoClear(modelName);
|
||||||
|
_addAssetToWorldInfoTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldInfoTimeout() {
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If list not empty, display last message in list (may already be displayed ) unless an error is being displayed.
|
||||||
|
If list empty, close the message box unless an error is being displayed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!_addAssetToWorldErrorTimer.isActive() && _addAssetToWorldMessageBox) {
|
||||||
|
if (_addAssetToWorldInfoKeys.length() > 0) {
|
||||||
|
_addAssetToWorldMessageBox->setProperty("text", "\n" + _addAssetToWorldInfoMessages.last());
|
||||||
|
} else {
|
||||||
|
disconnect(_addAssetToWorldMessageBox);
|
||||||
|
_addAssetToWorldMessageBox->setVisible(false);
|
||||||
|
_addAssetToWorldMessageBox->deleteLater();
|
||||||
|
_addAssetToWorldMessageBox = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Application::addAssetToWorldError(QString modelName, QString errorText) {
|
||||||
|
// Displays the most recent error message for a few seconds.
|
||||||
|
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If list has an entry for modelName, delete it.
|
||||||
|
Display errorText in message box.
|
||||||
|
Show message box if not already visible.
|
||||||
|
(Re)start error timer. ... onAddAssetToWorldErrorTimeout()
|
||||||
|
*/
|
||||||
|
|
||||||
|
addAssetToWorldInfoClear(modelName);
|
||||||
|
|
||||||
|
if (!_addAssetToWorldMessageBox) {
|
||||||
|
_addAssetToWorldMessageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION,
|
||||||
|
"Downloading Asset", "", QMessageBox::NoButton, QMessageBox::NoButton);
|
||||||
|
connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
_addAssetToWorldMessageBox->setProperty("text", "\n" + errorText);
|
||||||
|
_addAssetToWorldMessageBox->setVisible(true);
|
||||||
|
|
||||||
|
_addAssetToWorldErrorTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addAssetToWorldErrorTimeout() {
|
||||||
|
if (_aboutToQuit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If list is not empty, display message from last entry.
|
||||||
|
If list is empty, close the message box.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (_addAssetToWorldMessageBox) {
|
||||||
|
if (_addAssetToWorldInfoKeys.length() > 0) {
|
||||||
|
_addAssetToWorldMessageBox->setProperty("text", "\n" + _addAssetToWorldInfoMessages.last());
|
||||||
|
} else {
|
||||||
|
disconnect(_addAssetToWorldMessageBox);
|
||||||
|
_addAssetToWorldMessageBox->setVisible(false);
|
||||||
|
_addAssetToWorldMessageBox->deleteLater();
|
||||||
|
_addAssetToWorldMessageBox = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Application::addAssetToWorldMessageClose() {
|
||||||
|
// Clear messages, e.g., if Interface is being closed or domain changes.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Call if user manually closes message box.
|
||||||
|
Call if domain changes.
|
||||||
|
Call if application is shutting down.
|
||||||
|
|
||||||
|
Stop timers.
|
||||||
|
Close the message box if open.
|
||||||
|
Clear lists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_addAssetToWorldInfoTimer.stop();
|
||||||
|
_addAssetToWorldErrorTimer.stop();
|
||||||
|
|
||||||
|
if (_addAssetToWorldMessageBox) {
|
||||||
|
disconnect(_addAssetToWorldMessageBox);
|
||||||
|
_addAssetToWorldMessageBox->setVisible(false);
|
||||||
|
_addAssetToWorldMessageBox->deleteLater();
|
||||||
|
_addAssetToWorldMessageBox = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
_addAssetToWorldInfoKeys.clear();
|
||||||
|
_addAssetToWorldInfoMessages.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onAssetToWorldMessageBoxClosed() {
|
||||||
|
if (_addAssetToWorldMessageBox) {
|
||||||
|
// User manually closed message box; perhaps because it has become stuck, so reset all messages.
|
||||||
|
qInfo(interfaceapp) << "User manually closed download status message box";
|
||||||
|
disconnect(_addAssetToWorldMessageBox);
|
||||||
|
_addAssetToWorldMessageBox = nullptr;
|
||||||
|
addAssetToWorldMessageClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Application::handleUnzip(QString zipFile, QString unzipFile, bool autoAdd) {
|
||||||
|
if (autoAdd) {
|
||||||
|
if (!unzipFile.isEmpty()) {
|
||||||
|
addAssetToWorld(unzipFile);
|
||||||
|
} else {
|
||||||
|
addAssetToWorldUnzipFailure(zipFile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showAssetServerWidget(unzipFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Application::packageModel() {
|
void Application::packageModel() {
|
||||||
ModelPackager::package();
|
ModelPackager::package();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <QtCore/QPointer>
|
#include <QtCore/QPointer>
|
||||||
#include <QtCore/QSet>
|
#include <QtCore/QSet>
|
||||||
#include <QtCore/QStringList>
|
#include <QtCore/QStringList>
|
||||||
|
#include <QtQuick/QQuickItem>
|
||||||
|
|
||||||
#include <QtGui/QImage>
|
#include <QtGui/QImage>
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
#include <AbstractViewStateInterface.h>
|
#include <AbstractViewStateInterface.h>
|
||||||
#include <EntityEditPacketSender.h>
|
#include <EntityEditPacketSender.h>
|
||||||
#include <EntityTreeRenderer.h>
|
#include <EntityTreeRenderer.h>
|
||||||
|
#include <FileScriptingInterface.h>
|
||||||
#include <input-plugins/KeyboardMouseDevice.h>
|
#include <input-plugins/KeyboardMouseDevice.h>
|
||||||
#include <input-plugins/TouchscreenDevice.h>
|
#include <input-plugins/TouchscreenDevice.h>
|
||||||
#include <OctreeQuery.h>
|
#include <OctreeQuery.h>
|
||||||
|
@ -310,6 +312,20 @@ public slots:
|
||||||
void toggleRunningScriptsWidget() const;
|
void toggleRunningScriptsWidget() const;
|
||||||
Q_INVOKABLE void showAssetServerWidget(QString filePath = "");
|
Q_INVOKABLE void showAssetServerWidget(QString filePath = "");
|
||||||
|
|
||||||
|
// FIXME: Move addAssetToWorld* methods to own class?
|
||||||
|
void addAssetToWorldFromURL(QString url);
|
||||||
|
void addAssetToWorldFromURLRequestFinished();
|
||||||
|
void addAssetToWorld(QString filePath);
|
||||||
|
void addAssetToWorldUnzipFailure(QString filePath);
|
||||||
|
void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy);
|
||||||
|
void addAssetToWorldUpload(QString filePath, QString mapping);
|
||||||
|
void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash);
|
||||||
|
void addAssetToWorldAddEntity(QString filePath, QString mapping);
|
||||||
|
|
||||||
|
void handleUnzip(QString sourceFile, QString destinationFile, bool autoAdd);
|
||||||
|
|
||||||
|
FileScriptingInterface* getFileDownloadInterface() { return _fileDownload; }
|
||||||
|
|
||||||
void handleLocalServerConnection() const;
|
void handleLocalServerConnection() const;
|
||||||
void readArgumentsFromLocalSocket() const;
|
void readArgumentsFromLocalSocket() const;
|
||||||
|
|
||||||
|
@ -352,10 +368,17 @@ public slots:
|
||||||
|
|
||||||
static void runTests();
|
static void runTests();
|
||||||
|
|
||||||
|
void setKeyboardFocusHighlight(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions);
|
||||||
|
|
||||||
QUuid getKeyboardFocusEntity() const; // thread-safe
|
QUuid getKeyboardFocusEntity() const; // thread-safe
|
||||||
void setKeyboardFocusEntity(QUuid id);
|
void setKeyboardFocusEntity(QUuid id);
|
||||||
void setKeyboardFocusEntity(EntityItemID entityItemID);
|
void setKeyboardFocusEntity(EntityItemID entityItemID);
|
||||||
|
|
||||||
|
unsigned int getKeyboardFocusOverlay();
|
||||||
|
void setKeyboardFocusOverlay(unsigned int overlayID);
|
||||||
|
|
||||||
|
void addAssetToWorldMessageClose();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void showDesktop();
|
void showDesktop();
|
||||||
void clearDomainOctreeDetails();
|
void clearDomainOctreeDetails();
|
||||||
|
@ -392,6 +415,12 @@ private slots:
|
||||||
void updateDisplayMode();
|
void updateDisplayMode();
|
||||||
void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);
|
void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);
|
||||||
|
|
||||||
|
void addAssetToWorldCheckModelSize();
|
||||||
|
|
||||||
|
void onAssetToWorldMessageBoxClosed();
|
||||||
|
void addAssetToWorldInfoTimeout();
|
||||||
|
void addAssetToWorldErrorTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void initDisplay();
|
static void initDisplay();
|
||||||
void init();
|
void init();
|
||||||
|
@ -567,7 +596,8 @@ private:
|
||||||
|
|
||||||
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
|
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
|
||||||
|
|
||||||
ThreadSafeValueCache<EntityItemID> _keyboardFocusedItem;
|
ThreadSafeValueCache<EntityItemID> _keyboardFocusedEntity;
|
||||||
|
ThreadSafeValueCache<unsigned int> _keyboardFocusedOverlay;
|
||||||
quint64 _lastAcceptedKeyPress = 0;
|
quint64 _lastAcceptedKeyPress = 0;
|
||||||
bool _isForeground = true; // starts out assumed to be in foreground
|
bool _isForeground = true; // starts out assumed to be in foreground
|
||||||
bool _inPaint = false;
|
bool _inPaint = false;
|
||||||
|
@ -609,6 +639,22 @@ private:
|
||||||
model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ;
|
model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ;
|
||||||
gpu::TexturePointer _defaultSkyboxTexture;
|
gpu::TexturePointer _defaultSkyboxTexture;
|
||||||
gpu::TexturePointer _defaultSkyboxAmbientTexture;
|
gpu::TexturePointer _defaultSkyboxAmbientTexture;
|
||||||
|
|
||||||
|
QTimer _addAssetToWorldResizeTimer;
|
||||||
|
QHash<QUuid, int> _addAssetToWorldResizeList;
|
||||||
|
|
||||||
|
void addAssetToWorldInfo(QString modelName, QString infoText);
|
||||||
|
void addAssetToWorldInfoClear(QString modelName);
|
||||||
|
void addAssetToWorldInfoDone(QString modelName);
|
||||||
|
void addAssetToWorldError(QString modelName, QString errorText);
|
||||||
|
|
||||||
|
QQuickItem* _addAssetToWorldMessageBox{ nullptr };
|
||||||
|
QStringList _addAssetToWorldInfoKeys; // Model name
|
||||||
|
QStringList _addAssetToWorldInfoMessages; // Info message
|
||||||
|
QTimer _addAssetToWorldInfoTimer;
|
||||||
|
QTimer _addAssetToWorldErrorTimer;
|
||||||
|
|
||||||
|
FileScriptingInterface* _fileDownload;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -202,9 +202,8 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec
|
||||||
if (_texture && _texture->isLoaded()) {
|
if (_texture && _texture->isLoaded()) {
|
||||||
// Make sure position and rotation is updated.
|
// Make sure position and rotation is updated.
|
||||||
Transform transform = getTransform();
|
Transform transform = getTransform();
|
||||||
// XXX this code runs too often for this...
|
|
||||||
// applyTransformTo(transform, true);
|
// Don't call applyTransformTo() or setTransform() here because this code runs too frequently.
|
||||||
// setTransform(transform);
|
|
||||||
|
|
||||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||||
bool isNull = _fromImage.isNull();
|
bool isNull = _fromImage.isNull();
|
||||||
|
|
|
@ -294,6 +294,14 @@ QString Overlays::getOverlayType(unsigned int overlayId) const {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QObject* Overlays::getOverlayObject(unsigned int id) {
|
||||||
|
Overlay::Pointer thisOverlay = getOverlay(id);
|
||||||
|
if (thisOverlay) {
|
||||||
|
return qobject_cast<QObject*>(&(*thisOverlay));
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int Overlays::getParentPanel(unsigned int childId) const {
|
unsigned int Overlays::getParentPanel(unsigned int childId) const {
|
||||||
Overlay::Pointer overlay = getOverlay(childId);
|
Overlay::Pointer overlay = getOverlay(childId);
|
||||||
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
|
auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
|
||||||
|
@ -390,7 +398,7 @@ QScriptValue OverlayPropertyResultToScriptValue(QScriptEngine* engine, const Ove
|
||||||
if (!value.value.isValid()) {
|
if (!value.value.isValid()) {
|
||||||
return QScriptValue::UndefinedValue;
|
return QScriptValue::UndefinedValue;
|
||||||
}
|
}
|
||||||
return engine->newVariant(value.value);
|
return engine->toScriptValue(value.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPropertyResult& value) {
|
void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPropertyResult& value) {
|
||||||
|
@ -598,6 +606,38 @@ bool Overlays::isAddedOverlay(unsigned int id) {
|
||||||
return _overlaysHUD.contains(id) || _overlaysWorld.contains(id);
|
return _overlaysHUD.contains(id) || _overlaysWorld.contains(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Overlays::sendMousePressOnOverlay(unsigned int overlayID, const PointerEvent& event) {
|
||||||
|
emit mousePressOnOverlay(overlayID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::sendMouseReleaseOnOverlay(unsigned int overlayID, const PointerEvent& event) {
|
||||||
|
emit mouseReleaseOnOverlay(overlayID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::sendMouseMoveOnOverlay(unsigned int overlayID, const PointerEvent& event) {
|
||||||
|
emit mouseMoveOnOverlay(overlayID, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::sendHoverEnterOverlay(unsigned int id, PointerEvent event) {
|
||||||
|
emit hoverEnterOverlay(id, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::sendHoverOverOverlay(unsigned int id, PointerEvent event) {
|
||||||
|
emit hoverOverOverlay(id, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::sendHoverLeaveOverlay(unsigned int id, PointerEvent event) {
|
||||||
|
emit hoverLeaveOverlay(id, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Overlays::getKeyboardFocusOverlay() const {
|
||||||
|
return qApp->getKeyboardFocusOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::setKeyboardFocusOverlay(unsigned int id) {
|
||||||
|
qApp->setKeyboardFocusOverlay(id);
|
||||||
|
}
|
||||||
|
|
||||||
float Overlays::width() const {
|
float Overlays::width() const {
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
return offscreenUi->getWindow()->size().width();
|
return offscreenUi->getWindow()->size().width();
|
||||||
|
@ -607,3 +647,152 @@ float Overlays::height() const {
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
return offscreenUi->getWindow()->size().height();
|
return offscreenUi->getWindow()->size().height();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const uint32_t MOUSE_POINTER_ID = 0;
|
||||||
|
|
||||||
|
static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay,
|
||||||
|
const RayToOverlayIntersectionResult& rayPickResult) {
|
||||||
|
|
||||||
|
// Project the intersection point onto the local xy plane of the overlay.
|
||||||
|
float distance;
|
||||||
|
glm::vec3 planePosition = position;
|
||||||
|
glm::vec3 planeNormal = rotation * Vectors::UNIT_Z;
|
||||||
|
glm::vec3 overlayDimensions = glm::vec3(dimensions.x, dimensions.y, 0.0f);
|
||||||
|
glm::vec3 rayDirection = pickRay.direction;
|
||||||
|
glm::vec3 rayStart = pickRay.origin;
|
||||||
|
glm::vec3 p;
|
||||||
|
if (rayPlaneIntersection(planePosition, planeNormal, rayStart, rayDirection, distance)) {
|
||||||
|
p = rayStart + rayDirection * distance;
|
||||||
|
} else {
|
||||||
|
p = rayPickResult.intersection;
|
||||||
|
}
|
||||||
|
glm::vec3 localP = glm::inverse(rotation) * (p - position);
|
||||||
|
glm::vec3 normalizedP = (localP / overlayDimensions) + glm::vec3(0.5f);
|
||||||
|
return glm::vec2(normalizedP.x * overlayDimensions.x,
|
||||||
|
(1.0f - normalizedP.y) * overlayDimensions.y); // flip y-axis
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t toPointerButtons(const QMouseEvent& event) {
|
||||||
|
uint32_t buttons = 0;
|
||||||
|
buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0;
|
||||||
|
buttons |= event.buttons().testFlag(Qt::RightButton) ? PointerEvent::SecondaryButton : 0;
|
||||||
|
buttons |= event.buttons().testFlag(Qt::MiddleButton) ? PointerEvent::TertiaryButton : 0;
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
|
||||||
|
switch (event.button()) {
|
||||||
|
case Qt::LeftButton:
|
||||||
|
return PointerEvent::PrimaryButton;
|
||||||
|
case Qt::RightButton:
|
||||||
|
return PointerEvent::SecondaryButton;
|
||||||
|
case Qt::MiddleButton:
|
||||||
|
return PointerEvent::TertiaryButton;
|
||||||
|
default:
|
||||||
|
return PointerEvent::NoButtons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray,
|
||||||
|
RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType) {
|
||||||
|
|
||||||
|
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlay);
|
||||||
|
|
||||||
|
QReadLocker lock(&_lock);
|
||||||
|
auto position = thisOverlay->getPosition();
|
||||||
|
auto rotation = thisOverlay->getRotation();
|
||||||
|
auto dimensions = thisOverlay->getSize();
|
||||||
|
|
||||||
|
glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult);
|
||||||
|
PointerEvent pointerEvent(eventType, MOUSE_POINTER_ID,
|
||||||
|
pos2D, rayPickResult.intersection,
|
||||||
|
rayPickResult.surfaceNormal, ray.direction,
|
||||||
|
toPointerButton(*event), toPointerButtons(*event));
|
||||||
|
|
||||||
|
return pointerEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::mousePressEvent(QMouseEvent* event) {
|
||||||
|
PerformanceTimer perfTimer("Overlays::mousePressEvent");
|
||||||
|
|
||||||
|
PickRay ray = qApp->computePickRay(event->x(), event->y());
|
||||||
|
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray);
|
||||||
|
if (rayPickResult.intersects) {
|
||||||
|
_currentClickingOnOverlayID = rayPickResult.overlayID;
|
||||||
|
|
||||||
|
// Only Web overlays can have focus.
|
||||||
|
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentClickingOnOverlayID));
|
||||||
|
if (thisOverlay) {
|
||||||
|
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press);
|
||||||
|
emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
|
||||||
|
} else {
|
||||||
|
emit mousePressOffOverlay();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit mousePressOffOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
|
PerformanceTimer perfTimer("Overlays::mouseReleaseEvent");
|
||||||
|
|
||||||
|
PickRay ray = qApp->computePickRay(event->x(), event->y());
|
||||||
|
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray);
|
||||||
|
if (rayPickResult.intersects) {
|
||||||
|
|
||||||
|
// Only Web overlays can have focus.
|
||||||
|
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(rayPickResult.overlayID));
|
||||||
|
if (thisOverlay) {
|
||||||
|
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Release);
|
||||||
|
emit mouseReleaseOnOverlay(rayPickResult.overlayID, pointerEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::mouseMoveEvent(QMouseEvent* event) {
|
||||||
|
PerformanceTimer perfTimer("Overlays::mouseMoveEvent");
|
||||||
|
|
||||||
|
PickRay ray = qApp->computePickRay(event->x(), event->y());
|
||||||
|
RayToOverlayIntersectionResult rayPickResult = findRayIntersection(ray);
|
||||||
|
if (rayPickResult.intersects) {
|
||||||
|
|
||||||
|
// Only Web overlays can have focus.
|
||||||
|
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(rayPickResult.overlayID));
|
||||||
|
if (thisOverlay) {
|
||||||
|
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Move);
|
||||||
|
emit mouseMoveOnOverlay(rayPickResult.overlayID, pointerEvent);
|
||||||
|
|
||||||
|
// If previously hovering over a different overlay then leave hover on that overlay.
|
||||||
|
if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) {
|
||||||
|
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentHoverOverOverlayID));
|
||||||
|
if (thisOverlay) {
|
||||||
|
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Move);
|
||||||
|
emit hoverLeaveOverlay(_currentHoverOverOverlayID, pointerEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If hovering over a new overlay then enter hover on that overlay.
|
||||||
|
if (rayPickResult.overlayID != _currentHoverOverOverlayID) {
|
||||||
|
emit hoverEnterOverlay(rayPickResult.overlayID, pointerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover over current overlay.
|
||||||
|
emit hoverOverOverlay(rayPickResult.overlayID, pointerEvent);
|
||||||
|
|
||||||
|
_currentHoverOverOverlayID = rayPickResult.overlayID;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If previously hovering an overlay then leave hover.
|
||||||
|
if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID) {
|
||||||
|
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentHoverOverOverlayID));
|
||||||
|
if (thisOverlay) {
|
||||||
|
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Move);
|
||||||
|
emit hoverLeaveOverlay(_currentHoverOverOverlayID, pointerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentHoverOverOverlayID = UNKNOWN_OVERLAY_ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,11 +18,13 @@
|
||||||
#ifndef hifi_Overlays_h
|
#ifndef hifi_Overlays_h
|
||||||
#define hifi_Overlays_h
|
#define hifi_Overlays_h
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
#include <QReadWriteLock>
|
#include <QReadWriteLock>
|
||||||
#include <QScriptValue>
|
#include <QScriptValue>
|
||||||
|
|
||||||
#include "Overlay.h"
|
#include <PointerEvent.h>
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
#include "OverlayPanel.h"
|
#include "OverlayPanel.h"
|
||||||
#include "PanelAttachable.h"
|
#include "PanelAttachable.h"
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@ class RayToOverlayIntersectionResult {
|
||||||
public:
|
public:
|
||||||
RayToOverlayIntersectionResult();
|
RayToOverlayIntersectionResult();
|
||||||
bool intersects;
|
bool intersects;
|
||||||
int overlayID;
|
unsigned int overlayID;
|
||||||
float distance;
|
float distance;
|
||||||
BoxFace face;
|
BoxFace face;
|
||||||
glm::vec3 surfaceNormal;
|
glm::vec3 surfaceNormal;
|
||||||
|
@ -75,9 +77,13 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R
|
||||||
* @namespace Overlays
|
* @namespace Overlays
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const unsigned int UNKNOWN_OVERLAY_ID = 0;
|
||||||
|
|
||||||
class Overlays : public QObject {
|
class Overlays : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(unsigned int keyboardFocusOverlay READ getKeyboardFocusOverlay WRITE setKeyboardFocusOverlay)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Overlays();
|
Overlays();
|
||||||
|
|
||||||
|
@ -94,6 +100,10 @@ public:
|
||||||
unsigned int addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
|
unsigned int addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
|
||||||
unsigned int addOverlay(Overlay::Pointer overlay);
|
unsigned int addOverlay(Overlay::Pointer overlay);
|
||||||
|
|
||||||
|
void mousePressEvent(QMouseEvent* event);
|
||||||
|
void mouseReleaseEvent(QMouseEvent* event);
|
||||||
|
void mouseMoveEvent(QMouseEvent* event);
|
||||||
|
|
||||||
void cleanupAllOverlays();
|
void cleanupAllOverlays();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -147,6 +157,15 @@ public slots:
|
||||||
*/
|
*/
|
||||||
QString getOverlayType(unsigned int overlayId) const;
|
QString getOverlayType(unsigned int overlayId) const;
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Get the overlay Script object.
|
||||||
|
*
|
||||||
|
* @function Overlays.getOverlayObject
|
||||||
|
* @param {Overlays.OverlayID} overlayID The ID of the overlay to get the script object of.
|
||||||
|
* @return {Object} The script object for the overlay if found.
|
||||||
|
*/
|
||||||
|
QObject* getOverlayObject(unsigned int id);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Get the ID of the overlay at a particular point on the HUD/screen.
|
* Get the ID of the overlay at a particular point on the HUD/screen.
|
||||||
*
|
*
|
||||||
|
@ -239,6 +258,17 @@ public slots:
|
||||||
/// return true if there is a panel with that id else false
|
/// return true if there is a panel with that id else false
|
||||||
bool isAddedPanel(unsigned int id) { return _panels.contains(id); }
|
bool isAddedPanel(unsigned int id) { return _panels.contains(id); }
|
||||||
|
|
||||||
|
void sendMousePressOnOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void sendMouseReleaseOnOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void sendMouseMoveOnOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
|
||||||
|
void sendHoverEnterOverlay(unsigned int id, PointerEvent event);
|
||||||
|
void sendHoverOverOverlay(unsigned int id, PointerEvent event);
|
||||||
|
void sendHoverLeaveOverlay(unsigned int id, PointerEvent event);
|
||||||
|
|
||||||
|
unsigned int getKeyboardFocusOverlay() const;
|
||||||
|
void setKeyboardFocusOverlay(unsigned int id);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Emitted when an overlay is deleted
|
* Emitted when an overlay is deleted
|
||||||
|
@ -249,6 +279,15 @@ signals:
|
||||||
void overlayDeleted(unsigned int id);
|
void overlayDeleted(unsigned int id);
|
||||||
void panelDeleted(unsigned int id);
|
void panelDeleted(unsigned int id);
|
||||||
|
|
||||||
|
void mousePressOnOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void mouseReleaseOnOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void mouseMoveOnOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void mousePressOffOverlay();
|
||||||
|
|
||||||
|
void hoverEnterOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void hoverOverOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
void hoverLeaveOverlay(unsigned int overlayID, const PointerEvent& event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cleanupOverlaysToDelete();
|
void cleanupOverlaysToDelete();
|
||||||
|
|
||||||
|
@ -262,8 +301,12 @@ private:
|
||||||
QReadWriteLock _deleteLock;
|
QReadWriteLock _deleteLock;
|
||||||
QScriptEngine* _scriptEngine;
|
QScriptEngine* _scriptEngine;
|
||||||
bool _enabled = true;
|
bool _enabled = true;
|
||||||
|
|
||||||
|
PointerEvent calculatePointerEvent(Overlay::Pointer overlay, PickRay ray, RayToOverlayIntersectionResult rayPickResult,
|
||||||
|
QMouseEvent* event, PointerEvent::EventType eventType);
|
||||||
|
|
||||||
|
unsigned int _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID };
|
||||||
|
unsigned int _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // hifi_Overlays_h
|
#endif // hifi_Overlays_h
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//
|
//
|
||||||
// Web3DOverlay.cpp
|
// Web3DOverlay.cpp
|
||||||
//
|
//
|
||||||
//
|
|
||||||
// Created by Clement on 7/1/14.
|
// Created by Clement on 7/1/14.
|
||||||
// Modified and renamed by Zander Otavka on 8/4/15
|
// Modified and renamed by Zander Otavka on 8/4/15
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
@ -12,8 +11,12 @@
|
||||||
|
|
||||||
#include "Web3DOverlay.h"
|
#include "Web3DOverlay.h"
|
||||||
|
|
||||||
|
#include <Application.h>
|
||||||
|
|
||||||
|
#include <QQuickWindow>
|
||||||
#include <QtGui/QOpenGLContext>
|
#include <QtGui/QOpenGLContext>
|
||||||
#include <QtQuick/QQuickItem>
|
#include <QtQuick/QQuickItem>
|
||||||
|
#include <QtQml/QQmlContext>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <GeometryCache.h>
|
#include <GeometryCache.h>
|
||||||
|
@ -28,17 +31,24 @@
|
||||||
|
|
||||||
static const float DPI = 30.47f;
|
static const float DPI = 30.47f;
|
||||||
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||||
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
static const float METERS_TO_INCHES = 39.3701f;
|
||||||
|
static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
||||||
|
|
||||||
QString const Web3DOverlay::TYPE = "web3d";
|
QString const Web3DOverlay::TYPE = "web3d";
|
||||||
|
|
||||||
Web3DOverlay::Web3DOverlay() : _dpi(DPI) {
|
Web3DOverlay::Web3DOverlay() : _dpi(DPI) {
|
||||||
|
_touchDevice.setCapabilities(QTouchDevice::Position);
|
||||||
|
_touchDevice.setType(QTouchDevice::TouchScreen);
|
||||||
|
_touchDevice.setName("RenderableWebEntityItemTouchDevice");
|
||||||
|
_touchDevice.setMaximumTouchPoints(4);
|
||||||
|
|
||||||
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
||||||
}
|
}
|
||||||
|
|
||||||
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
||||||
Billboard3DOverlay(Web3DOverlay),
|
Billboard3DOverlay(Web3DOverlay),
|
||||||
_url(Web3DOverlay->_url),
|
_url(Web3DOverlay->_url),
|
||||||
|
_scriptURL(Web3DOverlay->_scriptURL),
|
||||||
_dpi(Web3DOverlay->_dpi),
|
_dpi(Web3DOverlay->_dpi),
|
||||||
_resolution(Web3DOverlay->_resolution)
|
_resolution(Web3DOverlay->_resolution)
|
||||||
{
|
{
|
||||||
|
@ -50,6 +60,19 @@ Web3DOverlay::~Web3DOverlay() {
|
||||||
_webSurface->pause();
|
_webSurface->pause();
|
||||||
_webSurface->disconnect(_connection);
|
_webSurface->disconnect(_connection);
|
||||||
|
|
||||||
|
QObject::disconnect(_mousePressConnection);
|
||||||
|
_mousePressConnection = QMetaObject::Connection();
|
||||||
|
QObject::disconnect(_mouseReleaseConnection);
|
||||||
|
_mouseReleaseConnection = QMetaObject::Connection();
|
||||||
|
QObject::disconnect(_mouseMoveConnection);
|
||||||
|
_mouseMoveConnection = QMetaObject::Connection();
|
||||||
|
QObject::disconnect(_hoverLeaveConnection);
|
||||||
|
_hoverLeaveConnection = QMetaObject::Connection();
|
||||||
|
|
||||||
|
QObject::disconnect(_emitScriptEventConnection);
|
||||||
|
_emitScriptEventConnection = QMetaObject::Connection();
|
||||||
|
QObject::disconnect(_webEventReceivedConnection);
|
||||||
|
_webEventReceivedConnection = QMetaObject::Connection();
|
||||||
|
|
||||||
// The lifetime of the QML surface MUST be managed by the main thread
|
// The lifetime of the QML surface MUST be managed by the main thread
|
||||||
// Additionally, we MUST use local variables copied by value, rather than
|
// Additionally, we MUST use local variables copied by value, rather than
|
||||||
|
@ -67,11 +90,15 @@ Web3DOverlay::~Web3DOverlay() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Web3DOverlay::update(float deltatime) {
|
void Web3DOverlay::update(float deltatime) {
|
||||||
|
// FIXME: applyTransformTo causes tablet overlay to detach from tablet entity.
|
||||||
|
// Perhaps rather than deleting the following code it should be run only if isFacingAvatar() is true?
|
||||||
|
/*
|
||||||
if (usecTimestampNow() > _transformExpiry) {
|
if (usecTimestampNow() > _transformExpiry) {
|
||||||
Transform transform = getTransform();
|
Transform transform = getTransform();
|
||||||
applyTransformTo(transform);
|
applyTransformTo(transform);
|
||||||
setTransform(transform);
|
setTransform(transform);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void Web3DOverlay::render(RenderArgs* args) {
|
void Web3DOverlay::render(RenderArgs* args) {
|
||||||
|
@ -92,21 +119,62 @@ void Web3DOverlay::render(RenderArgs* args) {
|
||||||
// and the current rendering load)
|
// and the current rendering load)
|
||||||
_webSurface->setMaxFps(10);
|
_webSurface->setMaxFps(10);
|
||||||
_webSurface->create(currentContext);
|
_webSurface->create(currentContext);
|
||||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||||
_webSurface->load("WebView.qml");
|
_webSurface->load("Web3DOverlay.qml");
|
||||||
_webSurface->resume();
|
_webSurface->resume();
|
||||||
_webSurface->getRootItem()->setProperty("url", _url);
|
_webSurface->getRootItem()->setProperty("url", _url);
|
||||||
|
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
|
||||||
|
_webSurface->getRootContext()->setContextProperty("ApplicationInterface", qApp);
|
||||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||||
currentContext->makeCurrent(currentSurface);
|
currentContext->makeCurrent(currentSurface);
|
||||||
|
|
||||||
|
auto forwardPointerEvent = [=](unsigned int overlayID, const PointerEvent& event) {
|
||||||
|
if (overlayID == getOverlayID()) {
|
||||||
|
handlePointerEvent(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_mousePressConnection = connect(&(qApp->getOverlays()), &Overlays::mousePressOnOverlay, forwardPointerEvent);
|
||||||
|
_mouseReleaseConnection = connect(&(qApp->getOverlays()), &Overlays::mouseReleaseOnOverlay, forwardPointerEvent);
|
||||||
|
_mouseMoveConnection = connect(&(qApp->getOverlays()), &Overlays::mouseMoveOnOverlay, forwardPointerEvent);
|
||||||
|
_hoverLeaveConnection = connect(&(qApp->getOverlays()), &Overlays::hoverLeaveOverlay,
|
||||||
|
[=](unsigned int overlayID, const PointerEvent& event) {
|
||||||
|
if (this->_pressed && this->getOverlayID() == overlayID) {
|
||||||
|
// If the user mouses off the overlay while the button is down, simulate a touch end.
|
||||||
|
QTouchEvent::TouchPoint point;
|
||||||
|
point.setId(event.getID());
|
||||||
|
point.setState(Qt::TouchPointReleased);
|
||||||
|
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
|
||||||
|
QPointF windowPoint(windowPos.x, windowPos.y);
|
||||||
|
point.setScenePos(windowPoint);
|
||||||
|
point.setPos(windowPoint);
|
||||||
|
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||||
|
touchPoints.push_back(point);
|
||||||
|
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased,
|
||||||
|
touchPoints);
|
||||||
|
touchEvent->setWindow(_webSurface->getWindow());
|
||||||
|
touchEvent->setDevice(&_touchDevice);
|
||||||
|
touchEvent->setTarget(_webSurface->getRootItem());
|
||||||
|
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_emitScriptEventConnection = connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
|
||||||
|
_webEventReceivedConnection = connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 size = _resolution / _dpi * INCHES_TO_METERS;
|
vec2 halfSize = getSize() / 2.0f;
|
||||||
vec2 halfSize = size / 2.0f;
|
|
||||||
vec4 color(toGlm(getColor()), getAlpha());
|
vec4 color(toGlm(getColor()), getAlpha());
|
||||||
|
|
||||||
Transform transform = getTransform();
|
Transform transform = getTransform();
|
||||||
|
|
||||||
|
// FIXME: applyTransformTo causes tablet overlay to detach from tablet entity.
|
||||||
|
// Perhaps rather than deleting the following code it should be run only if isFacingAvatar() is true?
|
||||||
|
/*
|
||||||
applyTransformTo(transform, true);
|
applyTransformTo(transform, true);
|
||||||
setTransform(transform);
|
setTransform(transform);
|
||||||
|
*/
|
||||||
|
|
||||||
if (glm::length2(getDimensions()) != 1.0f) {
|
if (glm::length2(getDimensions()) != 1.0f) {
|
||||||
transform.postScale(vec3(getDimensions(), 1.0f));
|
transform.postScale(vec3(getDimensions(), 1.0f));
|
||||||
}
|
}
|
||||||
|
@ -144,6 +212,78 @@ const render::ShapeKey Web3DOverlay::getShapeKey() {
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QObject* Web3DOverlay::getEventHandler() {
|
||||||
|
if (!_webSurface) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return _webSurface->getEventHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) {
|
||||||
|
if (!_webSurface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSurface->setProxyWindow(proxyWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Web3DOverlay::handlePointerEvent(const PointerEvent& event) {
|
||||||
|
if (!_webSurface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi);
|
||||||
|
QPointF windowPoint(windowPos.x, windowPos.y);
|
||||||
|
|
||||||
|
if (event.getType() == PointerEvent::Move) {
|
||||||
|
// Forward a mouse move event to the Web surface.
|
||||||
|
QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton,
|
||||||
|
Qt::NoButton, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getType() == PointerEvent::Press) {
|
||||||
|
this->_pressed = true;
|
||||||
|
} else if (event.getType() == PointerEvent::Release) {
|
||||||
|
this->_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QEvent::Type type;
|
||||||
|
Qt::TouchPointState touchPointState;
|
||||||
|
switch (event.getType()) {
|
||||||
|
case PointerEvent::Press:
|
||||||
|
type = QEvent::TouchBegin;
|
||||||
|
touchPointState = Qt::TouchPointPressed;
|
||||||
|
break;
|
||||||
|
case PointerEvent::Release:
|
||||||
|
type = QEvent::TouchEnd;
|
||||||
|
touchPointState = Qt::TouchPointReleased;
|
||||||
|
break;
|
||||||
|
case PointerEvent::Move:
|
||||||
|
default:
|
||||||
|
type = QEvent::TouchUpdate;
|
||||||
|
touchPointState = Qt::TouchPointMoved;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTouchEvent::TouchPoint point;
|
||||||
|
point.setId(event.getID());
|
||||||
|
point.setState(touchPointState);
|
||||||
|
point.setPos(windowPoint);
|
||||||
|
point.setScreenPos(windowPoint);
|
||||||
|
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||||
|
touchPoints.push_back(point);
|
||||||
|
|
||||||
|
QTouchEvent* touchEvent = new QTouchEvent(type);
|
||||||
|
touchEvent->setWindow(_webSurface->getWindow());
|
||||||
|
touchEvent->setDevice(&_touchDevice);
|
||||||
|
touchEvent->setTarget(_webSurface->getRootItem());
|
||||||
|
touchEvent->setTouchPoints(touchPoints);
|
||||||
|
touchEvent->setTouchPointStates(touchPointState);
|
||||||
|
|
||||||
|
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
||||||
|
}
|
||||||
|
|
||||||
void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
Billboard3DOverlay::setProperties(properties);
|
Billboard3DOverlay::setProperties(properties);
|
||||||
|
|
||||||
|
@ -155,6 +295,14 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto scriptURLValue = properties["scriptURL"];
|
||||||
|
if (scriptURLValue.isValid()) {
|
||||||
|
QString newScriptURL = scriptURLValue.toString();
|
||||||
|
if (newScriptURL != _scriptURL) {
|
||||||
|
setScriptURL(newScriptURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto resolution = properties["resolution"];
|
auto resolution = properties["resolution"];
|
||||||
if (resolution.isValid()) {
|
if (resolution.isValid()) {
|
||||||
bool valid;
|
bool valid;
|
||||||
|
@ -164,7 +312,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
auto dpi = properties["dpi"];
|
auto dpi = properties["dpi"];
|
||||||
if (dpi.isValid()) {
|
if (dpi.isValid()) {
|
||||||
_dpi = dpi.toFloat();
|
_dpi = dpi.toFloat();
|
||||||
|
@ -175,6 +322,12 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
|
||||||
if (property == "url") {
|
if (property == "url") {
|
||||||
return _url;
|
return _url;
|
||||||
}
|
}
|
||||||
|
if (property == "scriptURL") {
|
||||||
|
return _scriptURL;
|
||||||
|
}
|
||||||
|
if (property == "resolution") {
|
||||||
|
return vec2toVariant(_resolution);
|
||||||
|
}
|
||||||
if (property == "dpi") {
|
if (property == "dpi") {
|
||||||
return _dpi;
|
return _dpi;
|
||||||
}
|
}
|
||||||
|
@ -188,22 +341,38 @@ void Web3DOverlay::setURL(const QString& url) {
|
||||||
_webSurface->getRootItem()->setProperty("url", url);
|
_webSurface->getRootItem()->setProperty("url", url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Web3DOverlay::setScriptURL(const QString& scriptURL) {
|
||||||
|
_scriptURL = scriptURL;
|
||||||
|
if (_webSurface) {
|
||||||
|
AbstractViewStateInterface::instance()->postLambdaEvent([this, scriptURL] {
|
||||||
|
_webSurface->getRootItem()->setProperty("scriptURL", scriptURL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 Web3DOverlay::getSize() {
|
||||||
|
return _resolution / _dpi * INCHES_TO_METERS * getDimensions();
|
||||||
|
};
|
||||||
|
|
||||||
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||||
// FIXME - face and surfaceNormal not being returned
|
// FIXME - face and surfaceNormal not being returned
|
||||||
|
|
||||||
// Make sure position and rotation is updated.
|
// Don't call applyTransformTo() or setTransform() here because this code runs too frequently.
|
||||||
Transform transform;
|
|
||||||
applyTransformTo(transform, true);
|
|
||||||
setTransform(transform);
|
|
||||||
|
|
||||||
vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions());
|
|
||||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||||
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), size, distance);
|
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), getSize(), distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
Web3DOverlay* Web3DOverlay::createClone() const {
|
Web3DOverlay* Web3DOverlay::createClone() const {
|
||||||
return new Web3DOverlay(this);
|
return new Web3DOverlay(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Web3DOverlay::emitScriptEvent(const QVariant& message) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||||
|
} else {
|
||||||
|
emit scriptEventReceived(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
|
|
||||||
#include "Billboard3DOverlay.h"
|
#include "Billboard3DOverlay.h"
|
||||||
|
|
||||||
|
#include <QTouchEvent>
|
||||||
|
|
||||||
|
#include <PointerEvent.h>
|
||||||
|
|
||||||
class OffscreenQmlSurface;
|
class OffscreenQmlSurface;
|
||||||
|
|
||||||
class Web3DOverlay : public Billboard3DOverlay {
|
class Web3DOverlay : public Billboard3DOverlay {
|
||||||
|
@ -29,25 +33,51 @@ public:
|
||||||
|
|
||||||
virtual void update(float deltatime) override;
|
virtual void update(float deltatime) override;
|
||||||
|
|
||||||
|
QObject* getEventHandler();
|
||||||
|
void setProxyWindow(QWindow* proxyWindow);
|
||||||
|
void handlePointerEvent(const PointerEvent& event);
|
||||||
|
|
||||||
// setters
|
// setters
|
||||||
void setURL(const QString& url);
|
void setURL(const QString& url);
|
||||||
|
void setScriptURL(const QString& script);
|
||||||
|
|
||||||
void setProperties(const QVariantMap& properties) override;
|
void setProperties(const QVariantMap& properties) override;
|
||||||
QVariant getProperty(const QString& property) override;
|
QVariant getProperty(const QString& property) override;
|
||||||
|
|
||||||
|
glm::vec2 getSize();
|
||||||
|
|
||||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||||
|
|
||||||
virtual Web3DOverlay* createClone() const override;
|
virtual Web3DOverlay* createClone() const override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void emitScriptEvent(const QVariant& scriptMessage);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void scriptEventReceived(const QVariant& message);
|
||||||
|
void webEventReceived(const QVariant& message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||||
QMetaObject::Connection _connection;
|
QMetaObject::Connection _connection;
|
||||||
gpu::TexturePointer _texture;
|
gpu::TexturePointer _texture;
|
||||||
QString _url;
|
QString _url;
|
||||||
|
QString _scriptURL;
|
||||||
float _dpi;
|
float _dpi;
|
||||||
vec2 _resolution{ 640, 480 };
|
vec2 _resolution{ 640, 480 };
|
||||||
int _geometryId { 0 };
|
int _geometryId { 0 };
|
||||||
|
|
||||||
|
bool _pressed{ false };
|
||||||
|
QTouchDevice _touchDevice;
|
||||||
|
|
||||||
|
QMetaObject::Connection _mousePressConnection;
|
||||||
|
QMetaObject::Connection _mouseReleaseConnection;
|
||||||
|
QMetaObject::Connection _mouseMoveConnection;
|
||||||
|
QMetaObject::Connection _hoverLeaveConnection;
|
||||||
|
|
||||||
|
QMetaObject::Connection _emitScriptEventConnection;
|
||||||
|
QMetaObject::Connection _webEventReceivedConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Web3DOverlay_h
|
#endif // hifi_Web3DOverlay_h
|
||||||
|
|
|
@ -836,6 +836,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
||||||
// If the new hover entity does not match the previous hover entity then we are entering the new one
|
// If the new hover entity does not match the previous hover entity then we are entering the new one
|
||||||
// this is true if the _currentHoverOverEntityID is known or unknown
|
// this is true if the _currentHoverOverEntityID is known or unknown
|
||||||
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
if (rayPickResult.entityID != _currentHoverOverEntityID) {
|
||||||
|
emit hoverEnterEntity(rayPickResult.entityID, pointerEvent);
|
||||||
if (_entitiesScriptEngine) {
|
if (_entitiesScriptEngine) {
|
||||||
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", pointerEvent);
|
_entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", pointerEvent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -533,7 +533,8 @@ void RenderableModelEntityItem::update(const quint64& now) {
|
||||||
properties.setLastEdited(usecTimestampNow()); // we must set the edit time since we're editing it
|
properties.setLastEdited(usecTimestampNow()); // we must set the edit time since we're editing it
|
||||||
auto extents = _model->getMeshExtents();
|
auto extents = _model->getMeshExtents();
|
||||||
properties.setDimensions(extents.maximum - extents.minimum);
|
properties.setDimensions(extents.maximum - extents.minimum);
|
||||||
qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL());
|
qCDebug(entitiesrenderer) << "Autoresizing" << (!getName().isEmpty() ? getName() : getModelURL())
|
||||||
|
<< "from mesh extents";
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<EntityScriptingInterface>().data(), "editEntity",
|
QMetaObject::invokeMethod(DependencyManager::get<EntityScriptingInterface>().data(), "editEntity",
|
||||||
Qt::QueuedConnection,
|
Qt::QueuedConnection,
|
||||||
Q_ARG(QUuid, getEntityItemID()),
|
Q_ARG(QUuid, getEntityItemID()),
|
||||||
|
|
|
@ -326,8 +326,6 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
|
||||||
touchEvent->setTouchPoints(touchPoints);
|
touchEvent->setTouchPoints(touchPoints);
|
||||||
touchEvent->setTouchPointStates(touchPointState);
|
touchEvent->setTouchPointStates(touchPointState);
|
||||||
|
|
||||||
_lastTouchEvent = *touchEvent;
|
|
||||||
|
|
||||||
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,7 @@ private:
|
||||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||||
QMetaObject::Connection _connection;
|
QMetaObject::Connection _connection;
|
||||||
gpu::TexturePointer _texture;
|
gpu::TexturePointer _texture;
|
||||||
ivec2 _lastPress { INT_MIN };
|
|
||||||
bool _pressed{ false };
|
bool _pressed{ false };
|
||||||
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
|
||||||
uint64_t _lastRenderTime{ 0 };
|
uint64_t _lastRenderTime{ 0 };
|
||||||
QTouchDevice _touchDevice;
|
QTouchDevice _touchDevice;
|
||||||
|
|
||||||
|
|
|
@ -316,7 +316,7 @@ private:
|
||||||
float _localRenderAlpha;
|
float _localRenderAlpha;
|
||||||
bool _localRenderAlphaChanged;
|
bool _localRenderAlphaChanged;
|
||||||
bool _defaultSettings;
|
bool _defaultSettings;
|
||||||
bool _dimensionsInitialized = true; // Only false if creating an entity localy with no dimensions properties
|
bool _dimensionsInitialized = true; // Only false if creating an entity locally with no dimensions properties
|
||||||
|
|
||||||
// NOTE: The following are pseudo client only properties. They are only used in clients which can access
|
// NOTE: The following are pseudo client only properties. They are only used in clients which can access
|
||||||
// properties of model geometry. But these properties are not serialized like other properties.
|
// properties of model geometry. But these properties are not serialized like other properties.
|
||||||
|
|
|
@ -35,6 +35,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
|
||||||
connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged);
|
connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged);
|
||||||
connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged);
|
connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged);
|
||||||
connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged);
|
connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged);
|
||||||
|
connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
|
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
|
||||||
|
@ -63,6 +64,11 @@ bool EntityScriptingInterface::canRezTmp() {
|
||||||
return nodeList->getThisNodeCanRezTmp();
|
return nodeList->getThisNodeCanRezTmp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EntityScriptingInterface::canWriteAssets() {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
return nodeList->getThisNodeCanWriteAssets();
|
||||||
|
}
|
||||||
|
|
||||||
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
|
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
|
||||||
if (_entityTree) {
|
if (_entityTree) {
|
||||||
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
|
||||||
|
|
|
@ -110,6 +110,12 @@ public slots:
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE bool canRezTmp();
|
Q_INVOKABLE bool canRezTmp();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.canWriteAsseets
|
||||||
|
* @return {bool} `true` if the DomainServer will allow this Node/Avatar to write to the asset server
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE bool canWriteAssets();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Add a new entity with the specified properties. If `clientOnly` is true, the entity will
|
* Add a new entity with the specified properties. If `clientOnly` is true, the entity will
|
||||||
* not be sent to the server and will only be visible/accessible on the local client.
|
* not be sent to the server and will only be visible/accessible on the local client.
|
||||||
|
@ -282,6 +288,7 @@ signals:
|
||||||
void canAdjustLocksChanged(bool canAdjustLocks);
|
void canAdjustLocksChanged(bool canAdjustLocks);
|
||||||
void canRezChanged(bool canRez);
|
void canRezChanged(bool canRez);
|
||||||
void canRezTmpChanged(bool canRez);
|
void canRezTmpChanged(bool canRez);
|
||||||
|
void canWriteAssetsChanged(bool canWriteAssets);
|
||||||
|
|
||||||
void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
|
||||||
|
|
|
@ -26,3 +26,16 @@ void ResourceRequest::send() {
|
||||||
_state = InProgress;
|
_state = InProgress;
|
||||||
doSend();
|
doSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ResourceRequest::getResultString() const {
|
||||||
|
switch (_result) {
|
||||||
|
case Success: return "Success";
|
||||||
|
case Error: return "Error";
|
||||||
|
case Timeout: return "Timeout";
|
||||||
|
case ServerUnavailable: return "Server Unavailable";
|
||||||
|
case AccessDenied: return "Access Denied";
|
||||||
|
case InvalidURL: return "Invalid URL";
|
||||||
|
case NotFound: return "Not Found";
|
||||||
|
default: return "Unspecified Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ public:
|
||||||
QByteArray getData() { return _data; }
|
QByteArray getData() { return _data; }
|
||||||
State getState() const { return _state; }
|
State getState() const { return _state; }
|
||||||
Result getResult() const { return _result; }
|
Result getResult() const { return _result; }
|
||||||
|
QString getResultString() const;
|
||||||
QUrl getUrl() const { return _url; }
|
QUrl getUrl() const { return _url; }
|
||||||
bool loadedFromCache() const { return _loadedFromCache; }
|
bool loadedFromCache() const { return _loadedFromCache; }
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ FileScriptingInterface::FileScriptingInterface(QObject* parent) : QObject(parent
|
||||||
// nothing for now
|
// nothing for now
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileScriptingInterface::runUnzip(QString path, QUrl url) {
|
void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd) {
|
||||||
qDebug() << "Url that was downloaded: " + url.toString();
|
qDebug() << "Url that was downloaded: " + url.toString();
|
||||||
qDebug() << "Path where download is saved: " + path;
|
qDebug() << "Path where download is saved: " + path;
|
||||||
QString fileName = "/" + path.section("/", -1);
|
QString fileName = "/" + path.section("/", -1);
|
||||||
|
@ -44,15 +44,13 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString file = unzipFile(path, tempDir);
|
QString file = unzipFile(path, tempDir);
|
||||||
|
QString filename = QUrl::fromLocalFile(file).toString();
|
||||||
if (file != "") {
|
if (file != "") {
|
||||||
qDebug() << "Object file to upload: " + file;
|
qDebug() << "File to upload: " + filename;
|
||||||
QUrl url = QUrl::fromLocalFile(file);
|
|
||||||
emit unzipSuccess(url.toString());
|
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "unzip failed";
|
qDebug() << "Unzip failed";
|
||||||
}
|
}
|
||||||
qDebug() << "Removing temporary directory at: " + tempDir;
|
emit unzipResult(path, filename, autoAdd);
|
||||||
QDir(tempDir).removeRecursively();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix to check that we are only referring to a temporary directory
|
// fix to check that we are only referring to a temporary directory
|
||||||
|
@ -68,21 +66,6 @@ bool FileScriptingInterface::isTempDir(QString tempDir) {
|
||||||
return (testContainer == tempContainer);
|
return (testContainer == tempContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks whether the webview is displaying a Clara.io page for Marketplaces.qml
|
|
||||||
bool FileScriptingInterface::isClaraLink(QUrl url) {
|
|
||||||
return (url.toString().contains("clara.io") && !url.toString().contains("clara.io/signup"));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileScriptingInterface::isZippedFbx(QUrl url) {
|
|
||||||
return (url.toString().endsWith("fbx.zip"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// checks whether a user tries to download a file that is not in .fbx format
|
|
||||||
bool FileScriptingInterface::isZipped(QUrl url) {
|
|
||||||
return (url.toString().endsWith(".zip"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function is not in use
|
|
||||||
QString FileScriptingInterface::getTempDir() {
|
QString FileScriptingInterface::getTempDir() {
|
||||||
QTemporaryDir dir;
|
QTemporaryDir dir;
|
||||||
dir.setAutoRemove(false);
|
dir.setAutoRemove(false);
|
||||||
|
@ -108,7 +91,6 @@ void FileScriptingInterface::downloadZip(QString path, const QString link) {
|
||||||
request->send();
|
request->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QString FileScriptingInterface::unzipFile(QString path, QString tempDir) {
|
QString FileScriptingInterface::unzipFile(QString path, QString tempDir) {
|
||||||
|
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
|
|
|
@ -22,17 +22,13 @@ class FileScriptingInterface : public QObject {
|
||||||
public:
|
public:
|
||||||
FileScriptingInterface(QObject* parent);
|
FileScriptingInterface(QObject* parent);
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
bool isZippedFbx(QUrl url);
|
|
||||||
bool isZipped(QUrl url);
|
|
||||||
bool isClaraLink(QUrl url);
|
|
||||||
QString convertUrlToPath(QUrl url);
|
QString convertUrlToPath(QUrl url);
|
||||||
void runUnzip(QString path, QUrl url);
|
void runUnzip(QString path, QUrl url, bool autoAdd);
|
||||||
QString getTempDir();
|
QString getTempDir();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void unzipSuccess(QString url);
|
void unzipResult(QString zipFile, QString unzipFile, bool autoAdd);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isTempDir(QString tempDir);
|
bool isTempDir(QString tempDir);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "OffscreenUi.h"
|
#include "OffscreenUi.h"
|
||||||
|
|
||||||
static const char* const URL_PROPERTY = "source";
|
static const char* const URL_PROPERTY = "source";
|
||||||
|
static const char* const SCRIPT_PROPERTY = "scriptUrl";
|
||||||
|
|
||||||
// Method called by Qt scripts to create a new web window in the overlay
|
// Method called by Qt scripts to create a new web window in the overlay
|
||||||
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||||
|
@ -31,51 +32,6 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi
|
||||||
return engine->newQObject(retVal);
|
return engine->newQObject(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
|
|
||||||
if (QThread::currentThread() != thread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
|
|
||||||
} else {
|
|
||||||
emit scriptEventReceived(scriptMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
|
|
||||||
if (QThread::currentThread() != thread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
|
|
||||||
} else {
|
|
||||||
// Special case to handle raising and lowering the virtual keyboard.
|
|
||||||
const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";
|
|
||||||
const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";
|
|
||||||
const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";
|
|
||||||
QString messageString = webMessage.type() == QVariant::String ? webMessage.toString() : "";
|
|
||||||
if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {
|
|
||||||
setKeyboardRaised(asQuickItem(), true, messageString == RAISE_KEYBOARD_NUMERIC);
|
|
||||||
} else if (messageString == LOWER_KEYBOARD) {
|
|
||||||
setKeyboardRaised(asQuickItem(), false);
|
|
||||||
} else {
|
|
||||||
emit webEventReceived(webMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QmlWebWindowClass::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
|
|
||||||
if (!object) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QQuickItem* item = dynamic_cast<QQuickItem*>(object);
|
|
||||||
while (item) {
|
|
||||||
if (item->property("keyboardRaised").isValid()) {
|
|
||||||
if (item->property("punctuationMode").isValid()) {
|
|
||||||
item->setProperty("punctuationMode", QVariant(numeric));
|
|
||||||
}
|
|
||||||
item->setProperty("keyboardRaised", QVariant(raised));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
item = dynamic_cast<QQuickItem*>(item->parentItem());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QmlWebWindowClass::getURL() const {
|
QString QmlWebWindowClass::getURL() const {
|
||||||
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
|
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
|
||||||
if (_qmlWindow.isNull()) {
|
if (_qmlWindow.isNull()) {
|
||||||
|
@ -93,3 +49,11 @@ void QmlWebWindowClass::setURL(const QString& urlString) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QmlWebWindowClass::setScriptURL(const QString& script) {
|
||||||
|
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||||
|
if (!_qmlWindow.isNull()) {
|
||||||
|
_qmlWindow->setProperty(SCRIPT_PROPERTY, script);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -22,20 +22,13 @@ public:
|
||||||
public slots:
|
public slots:
|
||||||
QString getURL() const;
|
QString getURL() const;
|
||||||
void setURL(const QString& url);
|
void setURL(const QString& url);
|
||||||
|
void setScriptURL(const QString& script);
|
||||||
void emitScriptEvent(const QVariant& scriptMessage);
|
|
||||||
void emitWebEvent(const QVariant& webMessage);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void urlChanged();
|
void urlChanged();
|
||||||
void scriptEventReceived(const QVariant& message);
|
|
||||||
void webEventReceived(const QVariant& message);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString qmlSource() const override { return "QmlWebWindow.qml"; }
|
QString qmlSource() const override { return "QmlWebWindow.qml"; }
|
||||||
|
|
||||||
private:
|
|
||||||
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -150,6 +150,52 @@ void QmlWindowClass::sendToQml(const QVariant& message) {
|
||||||
QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QmlWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
|
||||||
|
} else {
|
||||||
|
emit scriptEventReceived(scriptMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlWindowClass::emitWebEvent(const QVariant& webMessage) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
|
||||||
|
} else {
|
||||||
|
// Special case to handle raising and lowering the virtual keyboard.
|
||||||
|
const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";
|
||||||
|
const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";
|
||||||
|
const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";
|
||||||
|
QString messageString = webMessage.type() == QVariant::String ? webMessage.toString() : "";
|
||||||
|
if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {
|
||||||
|
setKeyboardRaised(asQuickItem(), true, messageString == RAISE_KEYBOARD_NUMERIC);
|
||||||
|
} else if (messageString == LOWER_KEYBOARD) {
|
||||||
|
setKeyboardRaised(asQuickItem(), false);
|
||||||
|
} else {
|
||||||
|
emit webEventReceived(webMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlWindowClass::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
|
||||||
|
if (!object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem* item = dynamic_cast<QQuickItem*>(object);
|
||||||
|
while (item) {
|
||||||
|
if (item->property("keyboardRaised").isValid()) {
|
||||||
|
if (item->property("punctuationMode").isValid()) {
|
||||||
|
item->setProperty("punctuationMode", QVariant(numeric));
|
||||||
|
}
|
||||||
|
item->setProperty("keyboardRaised", QVariant(raised));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item = dynamic_cast<QQuickItem*>(item->parentItem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QmlWindowClass::~QmlWindowClass() {
|
QmlWindowClass::~QmlWindowClass() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,10 @@ public slots:
|
||||||
// Scripts can use this to send a message to the QML object
|
// Scripts can use this to send a message to the QML object
|
||||||
void sendToQml(const QVariant& message);
|
void sendToQml(const QVariant& message);
|
||||||
|
|
||||||
|
// QmlWindow content may include WebView requiring EventBridge.
|
||||||
|
void emitScriptEvent(const QVariant& scriptMessage);
|
||||||
|
void emitWebEvent(const QVariant& webMessage);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void visibleChanged();
|
void visibleChanged();
|
||||||
void positionChanged();
|
void positionChanged();
|
||||||
|
@ -61,6 +65,10 @@ signals:
|
||||||
// Scripts can connect to this signal to receive messages from the QML object
|
// Scripts can connect to this signal to receive messages from the QML object
|
||||||
void fromQml(const QVariant& message);
|
void fromQml(const QVariant& message);
|
||||||
|
|
||||||
|
// QmlWindow content may include WebView requiring EventBridge.
|
||||||
|
void scriptEventReceived(const QVariant& message);
|
||||||
|
void webEventReceived(const QVariant& message);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void hasMoved(QVector2D);
|
void hasMoved(QVector2D);
|
||||||
void hasClosed();
|
void hasClosed();
|
||||||
|
@ -81,6 +89,10 @@ protected:
|
||||||
bool _toolWindow { false };
|
bool _toolWindow { false };
|
||||||
QPointer<QObject> _qmlWindow;
|
QPointer<QObject> _qmlWindow;
|
||||||
QString _source;
|
QString _source;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// QmlWindow content may include WebView requiring EventBridge.
|
||||||
|
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -140,6 +140,10 @@ var ONE_VEC = {
|
||||||
|
|
||||||
var NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
|
var NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
|
||||||
|
|
||||||
|
var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 };
|
||||||
|
var INCHES_TO_METERS = 1.0 / 39.3701;
|
||||||
|
|
||||||
|
|
||||||
// these control how long an abandoned pointer line or action will hang around
|
// these control how long an abandoned pointer line or action will hang around
|
||||||
var ACTION_TTL = 15; // seconds
|
var ACTION_TTL = 15; // seconds
|
||||||
var ACTION_TTL_REFRESH = 5;
|
var ACTION_TTL_REFRESH = 5;
|
||||||
|
@ -186,6 +190,7 @@ var STATE_NEAR_TRIGGER = 4;
|
||||||
var STATE_FAR_TRIGGER = 5;
|
var STATE_FAR_TRIGGER = 5;
|
||||||
var STATE_HOLD = 6;
|
var STATE_HOLD = 6;
|
||||||
var STATE_ENTITY_TOUCHING = 7;
|
var STATE_ENTITY_TOUCHING = 7;
|
||||||
|
var STATE_OVERLAY_TOUCHING = 8;
|
||||||
|
|
||||||
var holdEnabled = true;
|
var holdEnabled = true;
|
||||||
var nearGrabEnabled = true;
|
var nearGrabEnabled = true;
|
||||||
|
@ -208,6 +213,8 @@ var mostRecentSearchingHand = RIGHT_HAND;
|
||||||
|
|
||||||
var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx";
|
var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx";
|
||||||
|
|
||||||
|
var HARDWARE_MOUSE_ID = 0; // Value reserved for hardware mouse.
|
||||||
|
|
||||||
CONTROLLER_STATE_MACHINE[STATE_OFF] = {
|
CONTROLLER_STATE_MACHINE[STATE_OFF] = {
|
||||||
name: "off",
|
name: "off",
|
||||||
enterMethod: "offEnter",
|
enterMethod: "offEnter",
|
||||||
|
@ -249,6 +256,12 @@ CONTROLLER_STATE_MACHINE[STATE_ENTITY_TOUCHING] = {
|
||||||
exitMethod: "entityTouchingExit",
|
exitMethod: "entityTouchingExit",
|
||||||
updateMethod: "entityTouching"
|
updateMethod: "entityTouching"
|
||||||
};
|
};
|
||||||
|
CONTROLLER_STATE_MACHINE[STATE_OVERLAY_TOUCHING] = {
|
||||||
|
name: "overlayTouching",
|
||||||
|
enterMethod: "overlayTouchingEnter",
|
||||||
|
exitMethod: "overlayTouchingExit",
|
||||||
|
updateMethod: "overlayTouching"
|
||||||
|
};
|
||||||
|
|
||||||
function distanceBetweenPointAndEntityBoundingBox(point, entityProps) {
|
function distanceBetweenPointAndEntityBoundingBox(point, entityProps) {
|
||||||
var entityXform = new Xform(entityProps.rotation, entityProps.position);
|
var entityXform = new Xform(entityProps.rotation, entityProps.position);
|
||||||
|
@ -273,27 +286,48 @@ function angleBetween(a, b) {
|
||||||
return Math.acos(Vec3.dot(Vec3.normalize(a), Vec3.normalize(b)));
|
return Math.acos(Vec3.dot(Vec3.normalize(a), Vec3.normalize(b)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectOntoEntityXYPlane(entityID, worldPos) {
|
function projectOntoXYPlane(worldPos, position, rotation, dimensions, registrationPoint) {
|
||||||
var props = entityPropertiesCache.getProps(entityID);
|
var invRot = Quat.inverse(rotation);
|
||||||
var invRot = Quat.inverse(props.rotation);
|
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position));
|
||||||
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, props.position));
|
var invDimensions = { x: 1 / dimensions.x,
|
||||||
var invDimensions = { x: 1 / props.dimensions.x,
|
y: 1 / dimensions.y,
|
||||||
y: 1 / props.dimensions.y,
|
z: 1 / dimensions.z };
|
||||||
z: 1 / props.dimensions.z };
|
var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), registrationPoint);
|
||||||
var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint);
|
return { x: normalizedPos.x * dimensions.x,
|
||||||
return { x: normalizedPos.x * props.dimensions.x,
|
y: (1 - normalizedPos.y) * dimensions.y }; // flip y-axis
|
||||||
y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handLaserIntersectEntity(entityID, start) {
|
function projectOntoEntityXYPlane(entityID, worldPos) {
|
||||||
|
var props = entityPropertiesCache.getProps(entityID);
|
||||||
|
return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
function projectOntoOverlayXYPlane(overlayID, worldPos) {
|
||||||
|
var position = Overlays.getProperty(overlayID, "position");
|
||||||
|
var rotation = Overlays.getProperty(overlayID, "rotation");
|
||||||
|
var dimensions;
|
||||||
|
|
||||||
|
var dpi = Overlays.getProperty(overlayID, "dpi");
|
||||||
|
if (dpi) {
|
||||||
|
// Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale.
|
||||||
|
var resolution = Overlays.getProperty(overlayID, "resolution");
|
||||||
|
resolution.z = 1; // Circumvent divide-by-zero.
|
||||||
|
var scale = Overlays.getProperty(overlayID, "dimensions");
|
||||||
|
dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale);
|
||||||
|
} else {
|
||||||
|
dimensions = Overlays.getProperty(overlayID, "dimensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handLaserIntersectItem(position, rotation, start) {
|
||||||
var worldHandPosition = start.position;
|
var worldHandPosition = start.position;
|
||||||
var worldHandRotation = start.orientation;
|
var worldHandRotation = start.orientation;
|
||||||
|
|
||||||
var props = entityPropertiesCache.getProps(entityID);
|
if (position) {
|
||||||
|
var planePosition = position;
|
||||||
if (props.position) {
|
var planeNormal = Vec3.multiplyQbyV(rotation, {x: 0, y: 0, z: 1.0});
|
||||||
var planePosition = props.position;
|
|
||||||
var planeNormal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1.0});
|
|
||||||
var rayStart = worldHandPosition;
|
var rayStart = worldHandPosition;
|
||||||
var rayDirection = Quat.getUp(worldHandRotation);
|
var rayDirection = Quat.getUp(worldHandRotation);
|
||||||
var intersectionInfo = rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection);
|
var intersectionInfo = rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection);
|
||||||
|
@ -318,6 +352,17 @@ function handLaserIntersectEntity(entityID, start) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handLaserIntersectEntity(entityID, start) {
|
||||||
|
var props = entityPropertiesCache.getProps(entityID);
|
||||||
|
return handLaserIntersectItem(props.position, props.rotation, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handLaserIntersectOverlay(overlayID, start) {
|
||||||
|
var position = Overlays.getProperty(overlayID, "position");
|
||||||
|
var rotation = Overlays.getProperty(overlayID, "rotation");
|
||||||
|
return handLaserIntersectItem(position, rotation, start);
|
||||||
|
}
|
||||||
|
|
||||||
function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) {
|
function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) {
|
||||||
var rayDirectionDotPlaneNormal = Vec3.dot(rayDirection, planeNormal);
|
var rayDirectionDotPlaneNormal = Vec3.dot(rayDirection, planeNormal);
|
||||||
if (rayDirectionDotPlaneNormal > 0.00001 || rayDirectionDotPlaneNormal < -0.00001) {
|
if (rayDirectionDotPlaneNormal > 0.00001 || rayDirectionDotPlaneNormal < -0.00001) {
|
||||||
|
@ -727,6 +772,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.actionID = null; // action this script created...
|
this.actionID = null; // action this script created...
|
||||||
this.grabbedEntity = null; // on this entity.
|
this.grabbedEntity = null; // on this entity.
|
||||||
|
this.grabbedOverlay = null;
|
||||||
this.state = STATE_OFF;
|
this.state = STATE_OFF;
|
||||||
this.pointer = null; // entity-id of line object
|
this.pointer = null; // entity-id of line object
|
||||||
this.entityActivated = false;
|
this.entityActivated = false;
|
||||||
|
@ -1159,6 +1205,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
var result = {
|
var result = {
|
||||||
entityID: null,
|
entityID: null,
|
||||||
|
overlayID: null,
|
||||||
searchRay: pickRay,
|
searchRay: pickRay,
|
||||||
distance: PICK_MAX_DISTANCE
|
distance: PICK_MAX_DISTANCE
|
||||||
};
|
};
|
||||||
|
@ -1424,6 +1471,7 @@ function MyController(hand) {
|
||||||
var name;
|
var name;
|
||||||
|
|
||||||
this.grabbedEntity = null;
|
this.grabbedEntity = null;
|
||||||
|
this.grabbedOverlay = null;
|
||||||
this.isInitialGrab = false;
|
this.isInitialGrab = false;
|
||||||
this.shouldResetParentOnRelease = false;
|
this.shouldResetParentOnRelease = false;
|
||||||
|
|
||||||
|
@ -1517,6 +1565,7 @@ function MyController(hand) {
|
||||||
name = entityPropertiesCache.getProps(entity).name;
|
name = entityPropertiesCache.getProps(entity).name;
|
||||||
|
|
||||||
if (Entities.keyboardFocusEntity != entity) {
|
if (Entities.keyboardFocusEntity != entity) {
|
||||||
|
Overlays.keyboardFocusOverlay = 0;
|
||||||
Entities.keyboardFocusEntity = entity;
|
Entities.keyboardFocusEntity = entity;
|
||||||
|
|
||||||
pointerEvent = {
|
pointerEvent = {
|
||||||
|
@ -1536,7 +1585,8 @@ function MyController(hand) {
|
||||||
// send mouse events for button highlights and tooltips.
|
// send mouse events for button highlights and tooltips.
|
||||||
if (this.hand == mostRecentSearchingHand || (this.hand !== mostRecentSearchingHand &&
|
if (this.hand == mostRecentSearchingHand || (this.hand !== mostRecentSearchingHand &&
|
||||||
this.getOtherHandController().state !== STATE_SEARCHING &&
|
this.getOtherHandController().state !== STATE_SEARCHING &&
|
||||||
this.getOtherHandController().state !== STATE_ENTITY_TOUCHING)) {
|
this.getOtherHandController().state !== STATE_ENTITY_TOUCHING &&
|
||||||
|
this.getOtherHandController().state !== STATE_OVERLAY_TOUCHING)) {
|
||||||
|
|
||||||
// most recently searching hand has priority over other hand, for the purposes of button highlighting.
|
// most recently searching hand has priority over other hand, for the purposes of button highlighting.
|
||||||
pointerEvent = {
|
pointerEvent = {
|
||||||
|
@ -1589,6 +1639,65 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var overlay;
|
||||||
|
|
||||||
|
if (rayPickInfo.overlayID) {
|
||||||
|
overlay = rayPickInfo.overlayID;
|
||||||
|
|
||||||
|
if (Overlays.keyboardFocusOverlay != overlay) {
|
||||||
|
Entities.keyboardFocusEntity = null;
|
||||||
|
Overlays.keyboardFocusOverlay = overlay;
|
||||||
|
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: HARDWARE_MOUSE_ID,
|
||||||
|
pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection),
|
||||||
|
pos3D: rayPickInfo.intersection,
|
||||||
|
normal: rayPickInfo.normal,
|
||||||
|
direction: rayPickInfo.searchRay.direction,
|
||||||
|
button: "None"
|
||||||
|
};
|
||||||
|
|
||||||
|
this.hoverOverlay = overlay;
|
||||||
|
Overlays.sendHoverEnterOverlay(overlay, pointerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send mouse events for button highlights and tooltips.
|
||||||
|
if (this.hand == mostRecentSearchingHand || (this.hand !== mostRecentSearchingHand &&
|
||||||
|
this.getOtherHandController().state !== STATE_SEARCHING &&
|
||||||
|
this.getOtherHandController().state !== STATE_ENTITY_TOUCHING &&
|
||||||
|
this.getOtherHandController().state !== STATE_OVERLAY_TOUCHING)) {
|
||||||
|
|
||||||
|
// most recently searching hand has priority over other hand, for the purposes of button highlighting.
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: HARDWARE_MOUSE_ID,
|
||||||
|
pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection),
|
||||||
|
pos3D: rayPickInfo.intersection,
|
||||||
|
normal: rayPickInfo.normal,
|
||||||
|
direction: rayPickInfo.searchRay.direction,
|
||||||
|
button: "None"
|
||||||
|
};
|
||||||
|
|
||||||
|
Overlays.sendMouseMoveOnOverlay(overlay, pointerEvent);
|
||||||
|
Overlays.sendHoverOverOverlay(overlay, pointerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.triggerSmoothedGrab() && !isEditing()) {
|
||||||
|
this.grabbedOverlay = overlay;
|
||||||
|
this.setState(STATE_OVERLAY_TOUCHING, "begin touching overlay '" + overlay + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this.hoverOverlay) {
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: HARDWARE_MOUSE_ID
|
||||||
|
};
|
||||||
|
Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent);
|
||||||
|
this.hoverOverlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateEquipHaptics(potentialEquipHotspot, handPosition);
|
this.updateEquipHaptics(potentialEquipHotspot, handPosition);
|
||||||
|
|
||||||
var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS);
|
var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS);
|
||||||
|
@ -2339,7 +2448,6 @@ function MyController(hand) {
|
||||||
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
Entities.sendClickReleaseOnEntity(this.grabbedEntity, pointerEvent);
|
||||||
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
|
Entities.sendHoverLeaveEntity(this.grabbedEntity, pointerEvent);
|
||||||
}
|
}
|
||||||
this.focusedEntity = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.entityTouching = function(dt) {
|
this.entityTouching = function(dt) {
|
||||||
|
@ -2359,6 +2467,7 @@ function MyController(hand) {
|
||||||
if (intersectInfo) {
|
if (intersectInfo) {
|
||||||
|
|
||||||
if (Entities.keyboardFocusEntity != this.grabbedEntity) {
|
if (Entities.keyboardFocusEntity != this.grabbedEntity) {
|
||||||
|
Overlays.keyboardFocusOverlay = 0;
|
||||||
Entities.keyboardFocusEntity = this.grabbedEntity;
|
Entities.keyboardFocusEntity = this.grabbedEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2393,6 +2502,108 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.overlayTouchingEnter = function () {
|
||||||
|
// Test for intersection between controller laser and Web overlay plane.
|
||||||
|
var intersectInfo =
|
||||||
|
handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true));
|
||||||
|
if (intersectInfo) {
|
||||||
|
var pointerEvent = {
|
||||||
|
type: "Press",
|
||||||
|
id: HARDWARE_MOUSE_ID,
|
||||||
|
pos2D: projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point),
|
||||||
|
pos3D: intersectInfo.point,
|
||||||
|
normal: intersectInfo.normal,
|
||||||
|
direction: intersectInfo.searchRay.direction,
|
||||||
|
button: "Primary",
|
||||||
|
isPrimaryHeld: true
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Overlays.sendMousePressOnOverlay(this.grabbedOverlay, pointerEvent);
|
||||||
|
|
||||||
|
this.touchingEnterTimer = 0;
|
||||||
|
this.touchingEnterPointerEvent = pointerEvent;
|
||||||
|
this.touchingEnterPointerEvent.button = "None";
|
||||||
|
this.deadspotExpired = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.overlayTouchingExit = function () {
|
||||||
|
// Test for intersection between controller laser and Web overlay plane.
|
||||||
|
var intersectInfo =
|
||||||
|
handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true));
|
||||||
|
if (intersectInfo) {
|
||||||
|
var pointerEvent;
|
||||||
|
if (this.deadspotExpired) {
|
||||||
|
pointerEvent = {
|
||||||
|
type: "Release",
|
||||||
|
id: HARDWARE_MOUSE_ID,
|
||||||
|
pos2D: projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point),
|
||||||
|
pos3D: intersectInfo.point,
|
||||||
|
normal: intersectInfo.normal,
|
||||||
|
direction: intersectInfo.searchRay.direction,
|
||||||
|
button: "Primary"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
pointerEvent = this.touchingEnterPointerEvent;
|
||||||
|
pointerEvent.type = "Release";
|
||||||
|
pointerEvent.button = "Primary";
|
||||||
|
pointerEvent.isPrimaryHeld = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent);
|
||||||
|
Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.overlayTouching = function (dt) {
|
||||||
|
this.touchingEnterTimer += dt;
|
||||||
|
|
||||||
|
if (!this.triggerSmoothedGrab()) {
|
||||||
|
this.setState(STATE_OFF, "released trigger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for intersection between controller laser and Web overlay plane.
|
||||||
|
var intersectInfo =
|
||||||
|
handLaserIntersectOverlay(this.grabbedOverlay, getControllerWorldLocation(this.handToController(), true));
|
||||||
|
if (intersectInfo) {
|
||||||
|
|
||||||
|
if (Overlays.keyboardFocusOverlay != this.grabbedOverlay) {
|
||||||
|
Entities.keyboardFocusEntity = null;
|
||||||
|
Overlays.keyboardFocusOverlay = this.grabbedOverlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: HARDWARE_MOUSE_ID,
|
||||||
|
pos2D: projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point),
|
||||||
|
pos3D: intersectInfo.point,
|
||||||
|
normal: intersectInfo.normal,
|
||||||
|
direction: intersectInfo.searchRay.direction,
|
||||||
|
button: "NoButtons",
|
||||||
|
isPrimaryHeld: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var POINTER_PRESS_TO_MOVE_DELAY = 0.15; // seconds
|
||||||
|
var POINTER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.05; // radians ~ 3 degrees
|
||||||
|
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
|
||||||
|
angleBetween(pointerEvent.direction, this.touchingEnterPointerEvent.direction) > POINTER_PRESS_TO_MOVE_DEADSPOT_ANGLE) {
|
||||||
|
Overlays.sendMouseMoveOnOverlay(this.grabbedOverlay, pointerEvent);
|
||||||
|
this.deadspotExpired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.intersectionDistance = intersectInfo.distance;
|
||||||
|
if (farGrabEnabled) {
|
||||||
|
this.searchIndicatorOn(intersectInfo.searchRay);
|
||||||
|
}
|
||||||
|
Reticle.setVisible(false);
|
||||||
|
} else {
|
||||||
|
this.setState(STATE_OFF, "grabbed overlay was destroyed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.release = function() {
|
this.release = function() {
|
||||||
this.turnOffVisualizations();
|
this.turnOffVisualizations();
|
||||||
|
|
||||||
|
@ -2434,6 +2645,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.actionID = null;
|
this.actionID = null;
|
||||||
this.grabbedEntity = null;
|
this.grabbedEntity = null;
|
||||||
|
this.grabbedOverlay = null;
|
||||||
this.grabbedHotspot = null;
|
this.grabbedHotspot = null;
|
||||||
|
|
||||||
if (this.triggerSmoothedGrab()) {
|
if (this.triggerSmoothedGrab()) {
|
||||||
|
|
|
@ -334,9 +334,14 @@ input[type=button].red {
|
||||||
}
|
}
|
||||||
input[type=button].blue {
|
input[type=button].blue {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #94132e;
|
background-color: #1080b8;
|
||||||
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
|
background: linear-gradient(#00b4ef 20%, #1080b8 100%);
|
||||||
}
|
}
|
||||||
|
input[type=button].white {
|
||||||
|
color: #121212;
|
||||||
|
background-color: #afafaf;
|
||||||
|
background: linear-gradient(#fff 20%, #afafaf 100%);
|
||||||
|
}
|
||||||
|
|
||||||
input[type=button]:enabled:hover {
|
input[type=button]:enabled:hover {
|
||||||
background: linear-gradient(#000, #000);
|
background: linear-gradient(#000, #000);
|
||||||
|
@ -350,6 +355,10 @@ input[type=button].blue:enabled:hover {
|
||||||
background: linear-gradient(#00b4ef, #00b4ef);
|
background: linear-gradient(#00b4ef, #00b4ef);
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
input[type=button].white:enabled:hover {
|
||||||
|
background: linear-gradient(#fff, #fff);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
input[type=button]:active {
|
input[type=button]:active {
|
||||||
background: linear-gradient(#343434, #343434);
|
background: linear-gradient(#343434, #343434);
|
||||||
|
@ -360,6 +369,9 @@ input[type=button].red:active {
|
||||||
input[type=button].blue:active {
|
input[type=button].blue:active {
|
||||||
background: linear-gradient(#1080b8, #1080b8);
|
background: linear-gradient(#1080b8, #1080b8);
|
||||||
}
|
}
|
||||||
|
input[type=button].white:active {
|
||||||
|
background: linear-gradient(#afafaf, #afafaf);
|
||||||
|
}
|
||||||
|
|
||||||
input[type=button]:disabled {
|
input[type=button]:disabled {
|
||||||
color: #252525;
|
color: #252525;
|
||||||
|
|
|
@ -22,11 +22,12 @@ body {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.marketplaces-intro-text {
|
.marketplaces-intro-text {
|
||||||
margin-bottom: 60px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
.marketplace-tile {
|
.marketplace-tile {
|
||||||
float:left;
|
float:left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
.marketplace-tile-first-column {
|
.marketplace-tile-first-column {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -75,6 +76,44 @@ body {
|
||||||
.marketplace-clara-steps > li {
|
.marketplace-clara-steps > li {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#marketplace-navigation {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
background: #00b4ef;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
#marketplace-navigation .glyph {
|
||||||
|
/*
|
||||||
|
// Target look but can't use font in injected script.
|
||||||
|
font-family: HiFi-Glyphs;
|
||||||
|
font-size: 40px;
|
||||||
|
margin-left: 20px;
|
||||||
|
*/
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right: 3px;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
#marketplace-navigation .text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 50px;
|
||||||
|
vertical-align: top;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
#marketplace-navigation input {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width:768px) {
|
@media (max-width:768px) {
|
||||||
.marketplace-tile-first-column {
|
.marketplace-tile-first-column {
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -89,10 +128,10 @@ body {
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
.tile-divider {
|
.tile-divider {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 0%;
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.marketplace-tile-image {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.marketplace-tile-image{
|
|
||||||
margin-bottom:15px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -56,7 +56,7 @@ function setUpKeyboardControl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputs = document.querySelectorAll("input[type=text], input[type=number], textarea");
|
var inputs = document.querySelectorAll("input[type=text], input[type=password], input[type=number], textarea");
|
||||||
for (var i = 0, length = inputs.length; i < length; i++) {
|
for (var i = 0, length = inputs.length; i < length; i++) {
|
||||||
inputs[i].addEventListener("focus", raiseKeyboard);
|
inputs[i].addEventListener("focus", raiseKeyboard);
|
||||||
inputs[i].addEventListener("blur", lowerKeyboard);
|
inputs[i].addEventListener("blur", lowerKeyboard);
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
function loaded() {
|
|
||||||
bindExploreButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindExploreButtons() {
|
|
||||||
$('#exploreClaraMarketplace').on('click', function() {
|
|
||||||
window.location = "https://clara.io/library?public=true"
|
|
||||||
})
|
|
||||||
$('#exploreHifiMarketplace').on('click', function() {
|
|
||||||
window.location = "http://www.highfidelity.com/marketplace"
|
|
||||||
})
|
|
||||||
}
|
|
246
scripts/system/html/js/marketplacesInject.js
Normal file
246
scripts/system/html/js/marketplacesInject.js
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
//
|
||||||
|
// marketplacesInject.js
|
||||||
|
//
|
||||||
|
// Created by David Rowe on 12 Nov 2016.
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Injected into marketplace Web pages.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
// Event bridge messages.
|
||||||
|
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
|
||||||
|
var GOTO_DIRECTORY = "GOTO_DIRECTORY";
|
||||||
|
var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
|
||||||
|
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
|
||||||
|
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
|
||||||
|
|
||||||
|
var canWriteAssets = false;
|
||||||
|
|
||||||
|
function injectCommonCode(isDirectoryPage) {
|
||||||
|
|
||||||
|
// Supporting styles from marketplaces.css.
|
||||||
|
// Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain.
|
||||||
|
$("head").append(
|
||||||
|
'<style>' +
|
||||||
|
'#marketplace-navigation { font-family: Arial, Helvetica, sans-serif; width: 100%; height: 50px; background: #00b4ef; position: fixed; bottom: 0; z-index: 1000; }' +
|
||||||
|
'#marketplace-navigation .glyph { margin-left: 20px; margin-right: 3px; font-family: sans-serif; color: #fff; font-size: 24px; line-height: 50px; }' +
|
||||||
|
'#marketplace-navigation .text { color: #fff; font-size: 18px; line-height: 50px; vertical-align: top; position: relative; top: 1px; }' +
|
||||||
|
'#marketplace-navigation input#back-button { position: absolute; left: 20px; margin-top: 12px; padding-left: 0; padding-right: 5px; }' +
|
||||||
|
'#marketplace-navigation input#all-markets { position: absolute; right: 20px; margin-top: 12px; padding-left: 15px; padding-right: 15px; }' +
|
||||||
|
'#marketplace-navigation .right { position: absolute; right: 20px; }' +
|
||||||
|
'</style>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Supporting styles from edit-style.css.
|
||||||
|
// Font family, size, and position adjusted because Raleway-Bold cannot be used cross-domain.
|
||||||
|
$("head").append(
|
||||||
|
'<style>' +
|
||||||
|
'input[type=button] { font-family: Arial, Helvetica, sans-serif; font-weight: bold; font-size: 12px; text-transform: uppercase; vertical-align: center; height: 28px; min-width: 100px; padding: 0 15px; border-radius: 5px; border: none; color: #fff; background-color: #000; background: linear-gradient(#343434 20%, #000 100%); cursor: pointer; }' +
|
||||||
|
'input[type=button].white { color: #121212; background-color: #afafaf; background: linear-gradient(#fff 20%, #afafaf 100%); }' +
|
||||||
|
'input[type=button].white:enabled:hover { background: linear-gradient(#fff, #fff); border: none; }' +
|
||||||
|
'input[type=button].white:active { background: linear-gradient(#afafaf, #afafaf); }' +
|
||||||
|
'</style>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Footer.
|
||||||
|
var isInitialHiFiPage = location.href === "https://metaverse.highfidelity.com/marketplace?";
|
||||||
|
$("body").append(
|
||||||
|
'<div id="marketplace-navigation">' +
|
||||||
|
(!isInitialHiFiPage ? '<input id="back-button" type="button" class="white" value="< Back" />' : '') +
|
||||||
|
(isInitialHiFiPage ? '<span class="glyph">🛈</span> <span class="text">See also other marketplaces.</span>' : '') +
|
||||||
|
(!isDirectoryPage ? '<input id="all-markets" type="button" class="white" value="See All Markets" />' : '') +
|
||||||
|
(isDirectoryPage ? '<span class="right"><span class="glyph">🛈</span> <span class="text">Select a marketplace to explore.</span><span>' : '') +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Footer actions.
|
||||||
|
$("#back-button").on("click", function () {
|
||||||
|
window.history.back();
|
||||||
|
});
|
||||||
|
$("#all-markets").on("click", function () {
|
||||||
|
EventBridge.emitWebEvent(GOTO_DIRECTORY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectDirectoryCode() {
|
||||||
|
|
||||||
|
// Remove e-mail hyperlink.
|
||||||
|
var letUsKnow = $("#letUsKnow");
|
||||||
|
letUsKnow.replaceWith(letUsKnow.html());
|
||||||
|
|
||||||
|
// Add button links.
|
||||||
|
$('#exploreClaraMarketplace').on('click', function () {
|
||||||
|
window.location = "https://clara.io/library?gameCheck=true&public=true"
|
||||||
|
});
|
||||||
|
$('#exploreHifiMarketplace').on('click', function () {
|
||||||
|
window.location = "http://www.highfidelity.com/marketplace"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectHiFiCode() {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateClaraCode() {
|
||||||
|
// Have to repeatedly update Clara page because its content can change dynamically without location.href changing.
|
||||||
|
|
||||||
|
// Clara library page.
|
||||||
|
if (location.href.indexOf("clara.io/library") !== -1) {
|
||||||
|
// Make entries navigate to "Image" view instead of default "Real Time" view.
|
||||||
|
var elements = $("a.thumbnail");
|
||||||
|
for (var i = 0, length = elements.length; i < length; i++) {
|
||||||
|
var value = elements[i].getAttribute("href");
|
||||||
|
if (value.slice(-6) !== "/image") {
|
||||||
|
elements[i].setAttribute("href", value + "/image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clara item page.
|
||||||
|
if (location.href.indexOf("clara.io/view/") !== -1) {
|
||||||
|
// Make site navigation links retain gameCheck etc. parameters.
|
||||||
|
var element = $("a[href^=\'/library\']")[0];
|
||||||
|
var parameters = "?gameCheck=true&public=true";
|
||||||
|
var href = element.getAttribute("href");
|
||||||
|
if (href.slice(-parameters.length) !== parameters) {
|
||||||
|
element.setAttribute("href", href + parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace download options with a single, "Download to High Fidelity" option.
|
||||||
|
var buttons = $("a.embed-button").parent("div");
|
||||||
|
if (buttons.length > 0) {
|
||||||
|
var downloadFBX = buttons.find("a[data-extension=\'fbx\']")[0];
|
||||||
|
downloadFBX.addEventListener("click", startAutoDownload);
|
||||||
|
var firstButton = buttons.children(":first-child")[0];
|
||||||
|
buttons[0].insertBefore(downloadFBX, firstButton);
|
||||||
|
downloadFBX.setAttribute("class", "btn btn-primary download");
|
||||||
|
downloadFBX.innerHTML = "<i class=\'glyphicon glyphicon-download-alt\'></i> Download to High Fidelity";
|
||||||
|
buttons.children(":nth-child(2), .btn-group , .embed-button").each(function () { this.remove(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the "Download to High Fidelity" button to be more visible on tablet.
|
||||||
|
if ($("#hifi-download-container").length === 0 && window.innerWidth < 700) {
|
||||||
|
// Moving the button stops the Clara.io download from starting so instead, make a visual copy in the right place
|
||||||
|
// and wire its click event to click the original button.
|
||||||
|
var downloadContainer = $('<div id="hifi-download-container"></div>');
|
||||||
|
$(".top-title .col-sm-4").append(downloadContainer);
|
||||||
|
var downloadButton = $("a[data-extension=\'fbx\']").clone();
|
||||||
|
downloadButton[0].addEventListener("click", function () { downloadFBX.click(); });
|
||||||
|
downloadContainer.append(downloadButton);
|
||||||
|
downloadFBX.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatic download to High Fidelity.
|
||||||
|
var downloadTimer;
|
||||||
|
function startAutoDownload(event) {
|
||||||
|
if (!canWriteAssets) {
|
||||||
|
console.log("Clara.io FBX file download cancelled because no permissions to write to Asset Server");
|
||||||
|
EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.scrollTo(0, 0); // Scroll to top ready for history.back().
|
||||||
|
if (!downloadTimer) {
|
||||||
|
downloadTimer = setInterval(autoDownload, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function autoDownload() {
|
||||||
|
if ($("div.download-body").length !== 0) {
|
||||||
|
var downloadButton = $("div.download-body a.download-file");
|
||||||
|
if (downloadButton.length > 0) {
|
||||||
|
clearInterval(downloadTimer);
|
||||||
|
downloadTimer = null;
|
||||||
|
var href = downloadButton[0].href;
|
||||||
|
EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + href);
|
||||||
|
console.log("Clara.io FBX file download initiated for " + href);
|
||||||
|
$("a.btn.cancel").click();
|
||||||
|
history.back(); // Remove history item created by clicking "download".
|
||||||
|
};
|
||||||
|
} else if ($("div#view-signup_login_dialog").length === 0) {
|
||||||
|
// Don't stop checking for button if user is asked to log in.
|
||||||
|
clearInterval(downloadTimer);
|
||||||
|
downloadTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectClaraCode() {
|
||||||
|
|
||||||
|
// Make space for marketplaces footer in Clara pages.
|
||||||
|
$("head").append(
|
||||||
|
'<style>' +
|
||||||
|
'#app { margin-bottom: 135px; }' +
|
||||||
|
'.footer { bottom: 50px; }' +
|
||||||
|
'</style>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Condense space.
|
||||||
|
$("head").append(
|
||||||
|
'<style>' +
|
||||||
|
'div.page-title { line-height: 1.2; font-size: 13px; }' +
|
||||||
|
'div.page-title-row { padding-bottom: 0; }' +
|
||||||
|
'</style>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move "Download to High Fidelity" button.
|
||||||
|
$("head").append(
|
||||||
|
'<style>' +
|
||||||
|
'#hifi-download-container { position: absolute; top: 6px; right: 16px; }' +
|
||||||
|
'</style>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update code injected per page displayed.
|
||||||
|
var updateClaraCodeInterval = undefined;
|
||||||
|
updateClaraCode();
|
||||||
|
updateClaraCodeInterval = setInterval(function () {
|
||||||
|
updateClaraCode();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
window.addEventListener("unload", function () {
|
||||||
|
clearInterval(updateClaraCodeInterval);
|
||||||
|
updateClaraCodeInterval = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoad() {
|
||||||
|
|
||||||
|
EventBridge.scriptEventReceived.connect(function (message) {
|
||||||
|
if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) {
|
||||||
|
canWriteAssets = message.slice(-4) === "true";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var DIRECTORY = 0;
|
||||||
|
var HIFI = 1;
|
||||||
|
var CLARA = 2;
|
||||||
|
var pageType = DIRECTORY;
|
||||||
|
|
||||||
|
if (location.href.indexOf("highfidelity.com/") !== -1) { pageType = HIFI; }
|
||||||
|
if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; }
|
||||||
|
|
||||||
|
injectCommonCode(pageType === DIRECTORY);
|
||||||
|
switch (pageType) {
|
||||||
|
case DIRECTORY:
|
||||||
|
injectDirectoryCode();
|
||||||
|
break;
|
||||||
|
case HIFI:
|
||||||
|
injectHiFiCode();
|
||||||
|
break;
|
||||||
|
case CLARA:
|
||||||
|
injectClaraCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load / unload.
|
||||||
|
window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
|
||||||
|
|
||||||
|
}());
|
|
@ -13,19 +13,12 @@
|
||||||
<link rel="stylesheet" type="text/css" href="css/edit-style.css">
|
<link rel="stylesheet" type="text/css" href="css/edit-style.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/marketplaces.css">
|
<link rel="stylesheet" type="text/css" href="css/marketplaces.css">
|
||||||
<script src="js/jquery-2.1.4.min.js"></script>
|
<script src="js/jquery-2.1.4.min.js"></script>
|
||||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
|
||||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
|
||||||
<script src="js/marketplaces.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body onload='loaded();'>
|
<body>
|
||||||
<div class="marketplaces-container">
|
<div class="marketplaces-container">
|
||||||
<h2 class="marketplaces-title">
|
<h2 class="marketplaces-title">Marketplaces</h2>
|
||||||
Marketplaces
|
|
||||||
</h2>
|
|
||||||
<div class="marketplaces-intro-text">
|
<div class="marketplaces-intro-text">
|
||||||
<p>
|
<p>You can bring content into High Fidelity from anywhere you want. Here are a few places that support direct import of content right now. If you'd like to suggest a Market to include here, <a id="letUsKnow" href="mailto:contact@highfidelity.io">let us know.</a></p>
|
||||||
You can bring content into High Fidelity forom anywhere you want. Here are a few places that support direct import of content right now. If you'd like to suggest a Market to include here, <a href="mailto:contact@highfidelity.io">let us know.</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="marketplace-tile">
|
<div class="marketplace-tile">
|
||||||
<div class="marketplace-tile-first-column">
|
<div class="marketplace-tile-first-column">
|
||||||
|
@ -34,7 +27,7 @@
|
||||||
<div class="marketplace-tile-second-column">
|
<div class="marketplace-tile-second-column">
|
||||||
<p class="marketplace-tile-description">This is the default High Fidelity marketplace. Viewing and downloading content from here is fully supported in Interface.</p>
|
<p class="marketplace-tile-description">This is the default High Fidelity marketplace. Viewing and downloading content from here is fully supported in Interface.</p>
|
||||||
<div class="exploreButton-holder">
|
<div class="exploreButton-holder">
|
||||||
<input class="blue exploreButton" type="button" value="Explore" id="exploreHifiMarketplace"></input></div>
|
<input class="blue exploreButton" type="button" value="Explore" id="exploreHifiMarketplace" />
|
||||||
</div>
|
</div>
|
||||||
<hr class="tile-divider">
|
<hr class="tile-divider">
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,21 +38,21 @@
|
||||||
<div class="marketplace-tile-second-column">
|
<div class="marketplace-tile-second-column">
|
||||||
<p class="marketplace-tile-description">Clara.io has thousands of models available for importing into High Fidelity. Follow these steps for the best experience:</p>
|
<p class="marketplace-tile-description">Clara.io has thousands of models available for importing into High Fidelity. Follow these steps for the best experience:</p>
|
||||||
<ol class="marketplace-clara-steps">
|
<ol class="marketplace-clara-steps">
|
||||||
<li><a href="http://www.clara.io/signup">Create an account here </a>or log in as an existing user.</li>
|
<li><a id="claraSignUp" href="http://www.clara.io/signup">Create an account here </a>or log in as an existing user.</li>
|
||||||
<li>Choose a model from the list and click Download -> Autodesk FBX.</li>
|
<li>Choose a model from the list and click “Download to High Fidelity”.</li>
|
||||||
<li>After the file processes, click Download.</li>
|
|
||||||
<li>Add the model to your asset server, then find it from the list and choose Add To World.</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
<div class="exploreButton-holder">
|
<div class="exploreButton-holder">
|
||||||
<input class="blue exploreButton" type="button" value="Explore" id="exploreClaraMarketplace"></input>
|
<input class="blue exploreButton" type="button" value="Explore" id="exploreClaraMarketplace" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="tile-divider">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Code similar to the following is injected; the following is included here to help with CSS styling.
|
||||||
|
<div id="marketplace-navigation">
|
||||||
|
<span class="glyph">🛈</span> <span class="text">Select a marketplace to explore.</span>
|
||||||
|
<input id="all-markets" type="button" class="white" value="See All Markets" />
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -11,7 +11,7 @@
|
||||||
var RAD_TO_DEG = 180 / Math.PI;
|
var RAD_TO_DEG = 180 / Math.PI;
|
||||||
var X_AXIS = {x: 1, y: 0, z: 0};
|
var X_AXIS = {x: 1, y: 0, z: 0};
|
||||||
var Y_AXIS = {x: 0, y: 1, z: 0};
|
var Y_AXIS = {x: 0, y: 1, z: 0};
|
||||||
var DEFAULT_DPI = 32;
|
var DEFAULT_DPI = 30;
|
||||||
var DEFAULT_WIDTH = 0.5;
|
var DEFAULT_WIDTH = 0.5;
|
||||||
|
|
||||||
var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx";
|
var TABLET_URL = "https://s3.amazonaws.com/hifi-public/tony/tablet.fbx";
|
||||||
|
@ -43,14 +43,16 @@ WebTablet = function (url, width, dpi, clientOnly) {
|
||||||
|
|
||||||
var ASPECT = 4.0 / 3.0;
|
var ASPECT = 4.0 / 3.0;
|
||||||
var WIDTH = width || DEFAULT_WIDTH;
|
var WIDTH = width || DEFAULT_WIDTH;
|
||||||
var HEIGHT = WIDTH * ASPECT;
|
var TABLET_HEIGHT_SCALE = 640 / 680; // Screen size of tablet entity isn't quite the desired aspect.
|
||||||
|
var HEIGHT = WIDTH * ASPECT * TABLET_HEIGHT_SCALE;
|
||||||
var DEPTH = 0.025;
|
var DEPTH = 0.025;
|
||||||
var DPI = dpi || DEFAULT_DPI;
|
var DPI = dpi || DEFAULT_DPI;
|
||||||
|
var SENSOR_TO_ROOM_MATRIX = -2;
|
||||||
|
|
||||||
var spawnInfo = calcSpawnInfo();
|
var spawnInfo = calcSpawnInfo();
|
||||||
|
|
||||||
var tabletEntityPosition = spawnInfo.position;
|
var tabletEntityPosition = spawnInfo.position;
|
||||||
var tabletEntityRotation = spawnInfo.rotation;
|
var tabletEntityRotation = spawnInfo.rotation;
|
||||||
|
|
||||||
this.tabletEntityID = Entities.addEntity({
|
this.tabletEntityID = Entities.addEntity({
|
||||||
name: "tablet",
|
name: "tablet",
|
||||||
type: "Model",
|
type: "Model",
|
||||||
|
@ -62,40 +64,50 @@ WebTablet = function (url, width, dpi, clientOnly) {
|
||||||
}),
|
}),
|
||||||
dimensions: {x: WIDTH, y: HEIGHT, z: DEPTH},
|
dimensions: {x: WIDTH, y: HEIGHT, z: DEPTH},
|
||||||
parentID: MyAvatar.sessionUUID,
|
parentID: MyAvatar.sessionUUID,
|
||||||
parentJointIndex: -2
|
parentJointIndex: SENSOR_TO_ROOM_MATRIX
|
||||||
}, clientOnly);
|
}, clientOnly);
|
||||||
|
|
||||||
var WEB_ENTITY_REDUCTION_FACTOR = {x: 0.78, y: 0.85};
|
var WEB_OVERLAY_Z_OFFSET = -0.01;
|
||||||
var WEB_ENTITY_Z_OFFSET = -0.01;
|
|
||||||
|
|
||||||
var webEntityRotation = Quat.multiply(spawnInfo.rotation, Quat.angleAxis(180, Y_AXIS));
|
var webOverlayRotation = Quat.multiply(spawnInfo.rotation, Quat.angleAxis(180, Y_AXIS));
|
||||||
var webEntityPosition = Vec3.sum(spawnInfo.position, Vec3.multiply(WEB_ENTITY_Z_OFFSET, Quat.getFront(webEntityRotation)));
|
var webOverlayPosition = Vec3.sum(spawnInfo.position, Vec3.multiply(WEB_OVERLAY_Z_OFFSET, Quat.getFront(webOverlayRotation)));
|
||||||
|
|
||||||
this.webEntityID = Entities.addEntity({
|
this.webOverlayID = Overlays.addOverlay("web3d", {
|
||||||
name: "web",
|
url: url,
|
||||||
type: "Web",
|
position: webOverlayPosition,
|
||||||
sourceUrl: url,
|
rotation: webOverlayRotation,
|
||||||
dimensions: {x: WIDTH * WEB_ENTITY_REDUCTION_FACTOR.x,
|
resolution: { x: 480, y: 640 },
|
||||||
y: HEIGHT * WEB_ENTITY_REDUCTION_FACTOR.y,
|
|
||||||
z: 0.1},
|
|
||||||
position: webEntityPosition,
|
|
||||||
rotation: webEntityRotation,
|
|
||||||
shapeType: "box",
|
|
||||||
dpi: DPI,
|
dpi: DPI,
|
||||||
|
color: { red: 255, green: 255, blue: 255 },
|
||||||
|
alpha: 1.0,
|
||||||
parentID: this.tabletEntityID,
|
parentID: this.tabletEntityID,
|
||||||
parentJointIndex: -1
|
parentJointIndex: -1
|
||||||
}, clientOnly);
|
});
|
||||||
|
|
||||||
this.state = "idle";
|
this.state = "idle";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WebTablet.prototype.setURL = function (url) {
|
||||||
|
Overlays.editOverlay(this.webOverlayID, { url: url });
|
||||||
|
};
|
||||||
|
|
||||||
|
WebTablet.prototype.setScriptURL = function (scriptURL) {
|
||||||
|
Overlays.editOverlay(this.webOverlayID, { scriptURL: scriptURL });
|
||||||
|
};
|
||||||
|
|
||||||
|
WebTablet.prototype.getOverlayObject = function () {
|
||||||
|
return Overlays.getOverlayObject(this.webOverlayID);
|
||||||
|
};
|
||||||
|
|
||||||
WebTablet.prototype.destroy = function () {
|
WebTablet.prototype.destroy = function () {
|
||||||
Entities.deleteEntity(this.webEntityID);
|
Overlays.deleteOverlay(this.webOverlayID);
|
||||||
Entities.deleteEntity(this.tabletEntityID);
|
Entities.deleteEntity(this.tabletEntityID);
|
||||||
};
|
};
|
||||||
|
|
||||||
WebTablet.prototype.pickle = function () {
|
WebTablet.prototype.pickle = function () {
|
||||||
return JSON.stringify({webEntityID: this.webEntityID, tabletEntityID: this.tabletEntityID});
|
return JSON.stringify({ webOverlayID: this.webOverlayID, tabletEntityID: this.tabletEntityID });
|
||||||
};
|
};
|
||||||
|
|
||||||
WebTablet.unpickle = function (string) {
|
WebTablet.unpickle = function (string) {
|
||||||
if (!string) {
|
if (!string) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
//
|
|
||||||
// clara.js
|
|
||||||
//
|
|
||||||
// Created by Eric Levin on 8 Jan 2016
|
|
||||||
// Edited by Elisa Lupin-Jimenez on 23 Aug 2016
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
|
|
||||||
(function() { // BEGIN LOCAL_SCOPE
|
|
||||||
|
|
||||||
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
|
|
||||||
var qml = Script.resolvePath("../../../resources/qml/Marketplaces.qml")
|
|
||||||
|
|
||||||
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
|
|
||||||
|
|
||||||
var marketplaceWindow = new OverlayWindow({
|
|
||||||
title: "Marketplace",
|
|
||||||
source: qml,
|
|
||||||
width: 1000,
|
|
||||||
height: 900,
|
|
||||||
toolWindow: false,
|
|
||||||
visible: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
var toolHeight = 50;
|
|
||||||
var toolWidth = 50;
|
|
||||||
var TOOLBAR_MARGIN_Y = 0;
|
|
||||||
|
|
||||||
|
|
||||||
function showMarketplace(marketplaceID) {
|
|
||||||
var url = MARKETPLACE_URL;
|
|
||||||
if (marketplaceID) {
|
|
||||||
url = url + "/items/" + marketplaceID;
|
|
||||||
}
|
|
||||||
marketplaceWindow.setVisible(true);
|
|
||||||
|
|
||||||
UserActivityLogger.openedMarketplace();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMarketplace() {
|
|
||||||
marketplaceWindow.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMarketplace() {
|
|
||||||
if (marketplaceWindow.visible) {
|
|
||||||
hideMarketplace();
|
|
||||||
} else {
|
|
||||||
showMarketplace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
|
||||||
|
|
||||||
var browseExamplesButton = toolBar.addButton({
|
|
||||||
imageURL: toolIconUrl + "market.svg",
|
|
||||||
objectName: "marketplace",
|
|
||||||
buttonState: 1,
|
|
||||||
defaultState: 1,
|
|
||||||
hoverState: 3,
|
|
||||||
alpha: 0.9
|
|
||||||
});
|
|
||||||
|
|
||||||
function onExamplesWindowVisibilityChanged() {
|
|
||||||
browseExamplesButton.writeProperty('buttonState', marketplaceWindow.visible ? 0 : 1);
|
|
||||||
browseExamplesButton.writeProperty('defaultState', marketplaceWindow.visible ? 0 : 1);
|
|
||||||
browseExamplesButton.writeProperty('hoverState', marketplaceWindow.visible ? 2 : 3);
|
|
||||||
}
|
|
||||||
function onClick() {
|
|
||||||
toggleMarketplace();
|
|
||||||
}
|
|
||||||
browseExamplesButton.clicked.connect(onClick);
|
|
||||||
marketplaceWindow.visibleChanged.connect(onExamplesWindowVisibilityChanged);
|
|
||||||
|
|
||||||
Script.scriptEnding.connect(function () {
|
|
||||||
toolBar.removeButton("marketplace");
|
|
||||||
browseExamplesButton.clicked.disconnect(onClick);
|
|
||||||
marketplaceWindow.visibleChanged.disconnect(onExamplesWindowVisibilityChanged);
|
|
||||||
});
|
|
||||||
|
|
||||||
}()); // END LOCAL_SCOPE
|
|
178
scripts/system/marketplaces/marketplaces.js
Normal file
178
scripts/system/marketplaces/marketplaces.js
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
//
|
||||||
|
// marketplaces.js
|
||||||
|
//
|
||||||
|
// Created by Eric Levin on 8 Jan 2016
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
(function() { // BEGIN LOCAL_SCOPE
|
||||||
|
|
||||||
|
/* global WebTablet */
|
||||||
|
Script.include("../libraries/WebTablet.js");
|
||||||
|
|
||||||
|
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
|
||||||
|
|
||||||
|
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
|
||||||
|
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
|
||||||
|
var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
|
||||||
|
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
|
||||||
|
|
||||||
|
// Event bridge messages.
|
||||||
|
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
|
||||||
|
var GOTO_DIRECTORY = "GOTO_DIRECTORY";
|
||||||
|
var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
|
||||||
|
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
|
||||||
|
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
|
||||||
|
var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
|
||||||
|
|
||||||
|
var marketplaceWindow = new OverlayWebWindow({
|
||||||
|
title: "Marketplace",
|
||||||
|
source: "about:blank",
|
||||||
|
width: 900,
|
||||||
|
height: 700,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
marketplaceWindow.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL);
|
||||||
|
marketplaceWindow.webEventReceived.connect(function (message) {
|
||||||
|
if (message === GOTO_DIRECTORY) {
|
||||||
|
marketplaceWindow.setURL(MARKETPLACES_URL);
|
||||||
|
}
|
||||||
|
if (message === QUERY_CAN_WRITE_ASSETS) {
|
||||||
|
marketplaceWindow.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
|
||||||
|
}
|
||||||
|
if (message === WARN_USER_NO_PERMISSIONS) {
|
||||||
|
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var toolHeight = 50;
|
||||||
|
var toolWidth = 50;
|
||||||
|
var TOOLBAR_MARGIN_Y = 0;
|
||||||
|
var marketplaceVisible = false;
|
||||||
|
var marketplaceWebTablet;
|
||||||
|
|
||||||
|
// We persist clientOnly data in the .ini file, and reconstitute it on restart.
|
||||||
|
// To keep things consistent, we pickle the tablet data in Settings, and kill any existing such on restart and domain change.
|
||||||
|
var persistenceKey = "io.highfidelity.lastDomainTablet";
|
||||||
|
|
||||||
|
function shouldShowWebTablet() {
|
||||||
|
var rightPose = Controller.getPoseValue(Controller.Standard.RightHand);
|
||||||
|
var leftPose = Controller.getPoseValue(Controller.Standard.LeftHand);
|
||||||
|
var hasHydra = !!Controller.Hardware.Hydra;
|
||||||
|
return HMD.active && (leftPose.valid || rightPose.valid || hasHydra);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMarketplace() {
|
||||||
|
if (shouldShowWebTablet()) {
|
||||||
|
updateButtonState(true);
|
||||||
|
marketplaceWebTablet = new WebTablet(MARKETPLACE_URL_INITIAL, null, null, true);
|
||||||
|
Settings.setValue(persistenceKey, marketplaceWebTablet.pickle());
|
||||||
|
marketplaceWebTablet.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL);
|
||||||
|
marketplaceWebTablet.getOverlayObject().webEventReceived.connect(function (message) {
|
||||||
|
if (message === GOTO_DIRECTORY) {
|
||||||
|
marketplaceWebTablet.setURL(MARKETPLACES_URL);
|
||||||
|
}
|
||||||
|
if (message === QUERY_CAN_WRITE_ASSETS) {
|
||||||
|
marketplaceWebTablet.getOverlayObject().emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
|
||||||
|
}
|
||||||
|
if (message === WARN_USER_NO_PERMISSIONS) {
|
||||||
|
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
marketplaceWindow.setURL(MARKETPLACE_URL_INITIAL);
|
||||||
|
marketplaceWindow.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
marketplaceVisible = true;
|
||||||
|
UserActivityLogger.openedMarketplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTablet(tablet) {
|
||||||
|
if (!tablet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateButtonState(false);
|
||||||
|
tablet.destroy();
|
||||||
|
marketplaceWebTablet = null;
|
||||||
|
Settings.setValue(persistenceKey, "");
|
||||||
|
}
|
||||||
|
function clearOldTablet() { // If there was a tablet from previous domain or session, kill it and let it be recreated
|
||||||
|
var tablet = WebTablet.unpickle(Settings.getValue(persistenceKey, ""));
|
||||||
|
hideTablet(tablet);
|
||||||
|
}
|
||||||
|
function hideMarketplace() {
|
||||||
|
if (marketplaceWindow.visible) {
|
||||||
|
marketplaceWindow.setVisible(false);
|
||||||
|
marketplaceWindow.setURL("about:blank");
|
||||||
|
} else if (marketplaceWebTablet) {
|
||||||
|
hideTablet(marketplaceWebTablet);
|
||||||
|
}
|
||||||
|
marketplaceVisible = false;
|
||||||
|
}
|
||||||
|
marketplaceWindow.closed.connect(function () {
|
||||||
|
marketplaceWindow.setURL("about:blank");
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleMarketplace() {
|
||||||
|
if (marketplaceVisible) {
|
||||||
|
hideMarketplace();
|
||||||
|
} else {
|
||||||
|
showMarketplace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||||
|
|
||||||
|
var browseExamplesButton = toolBar.addButton({
|
||||||
|
imageURL: toolIconUrl + "market.svg",
|
||||||
|
objectName: "marketplace",
|
||||||
|
buttonState: 1,
|
||||||
|
defaultState: 1,
|
||||||
|
hoverState: 3,
|
||||||
|
alpha: 0.9
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateButtonState(visible) {
|
||||||
|
browseExamplesButton.writeProperty('buttonState', visible ? 0 : 1);
|
||||||
|
browseExamplesButton.writeProperty('defaultState', visible ? 0 : 1);
|
||||||
|
browseExamplesButton.writeProperty('hoverState', visible ? 2 : 3);
|
||||||
|
}
|
||||||
|
function onMarketplaceWindowVisibilityChanged() {
|
||||||
|
updateButtonState(marketplaceWindow.visible);
|
||||||
|
marketplaceVisible = marketplaceWindow.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCanWriteAssetsChanged() {
|
||||||
|
var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
|
||||||
|
if (marketplaceWindow.visible) {
|
||||||
|
marketplaceWindow.emitScriptEvent(message);
|
||||||
|
}
|
||||||
|
if (marketplaceWebTablet) {
|
||||||
|
marketplaceWebTablet.getOverlayObject().emitScriptEvent(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
toggleMarketplace();
|
||||||
|
}
|
||||||
|
|
||||||
|
browseExamplesButton.clicked.connect(onClick);
|
||||||
|
marketplaceWindow.visibleChanged.connect(onMarketplaceWindowVisibilityChanged);
|
||||||
|
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
|
||||||
|
|
||||||
|
clearOldTablet(); // Run once at startup, in case there's anything laying around from a crash.
|
||||||
|
// We could also optionally do something like Window.domainChanged.connect(function () {Script.setTimeout(clearOldTablet, 2000)}),
|
||||||
|
// but the HUD version stays around, so lets do the same.
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function () {
|
||||||
|
toolBar.removeButton("marketplace");
|
||||||
|
browseExamplesButton.clicked.disconnect(onClick);
|
||||||
|
marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged);
|
||||||
|
Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
|
||||||
|
});
|
||||||
|
|
||||||
|
}()); // END LOCAL_SCOPE
|
Loading…
Reference in a new issue