3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 04:15:36 +02:00

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

This commit is contained in:
Sam Gateau 2018-02-26 09:51:23 -08:00
commit 8e4ed40246
94 changed files with 1770 additions and 970 deletions
assignment-client/src/audio
interface
libraries
scripts
unpublishedScripts/marketplace
record
shapes/modules

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AudioMixer.h"
#include <thread>
#include <QtCore/QJsonArray>
@ -36,8 +38,6 @@
#include "AvatarAudioStream.h"
#include "InjectedAudioStream.h"
#include "AudioMixer.h"
static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance)
static const int DISABLE_STATIC_JITTER_FRAMES = -1;
static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f;

View file

@ -18,6 +18,8 @@
#include <ThreadedAssignment.h>
#include <UUIDHasher.h>
#include <plugins/Forward.h>
#include "AudioMixerStats.h"
#include "AudioMixerSlavePool.h"

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AudioMixerClientData.h"
#include <random>
#include <QtCore/QDebug>
@ -22,8 +24,6 @@
#include "AudioLogging.h"
#include "AudioHelpers.h"
#include "AudioMixer.h"
#include "AudioMixerClientData.h"
AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
NodeData(nodeID),

View file

@ -21,6 +21,7 @@
#include <AudioLimiter.h>
#include <UUIDHasher.h>
#include <plugins/Forward.h>
#include <plugins/CodecPlugin.h>
#include "PositionalAudioStream.h"

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AudioMixerSlave.h"
#include <algorithm>
#include <glm/glm.hpp>
@ -34,8 +36,6 @@
#include "InjectedAudioStream.h"
#include "AudioHelpers.h"
#include "AudioMixerSlave.h"
using AudioStreamMap = AudioMixerClientData::AudioStreamMap;
// packet helpers

View file

@ -14,8 +14,8 @@ import "../styles-uit"
Item {
property int colorScheme: 0;
readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray ];
readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray ];
readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ];
readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ];
// Size
height: colorScheme === 0 ? 2 : 1;

View file

@ -34,11 +34,10 @@ TextField {
placeholderText: textField.placeholderText
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; }
font.family: firaSansSemiBold.name
font.family: firaSansRegular.name
font.pixelSize: hifi.fontSizes.textFieldInput
font.italic: textField.text == ""
height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered.
property alias textFieldLabel: textFieldLabel

View file

@ -28,7 +28,7 @@ Rectangle {
id: root;
objectName: "checkout"
property string activeView: "initialize";
property bool purchasesReceived: false;
property bool ownershipStatusReceived: false;
property bool balanceReceived: false;
property string itemName;
property string itemId;
@ -37,12 +37,18 @@ Rectangle {
property double balanceAfterPurchase;
property bool alreadyOwned: false;
property int itemPrice: -1;
property bool itemIsJson: true;
property bool isCertified;
property string itemType;
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"];
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"];
property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"]
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
property bool shouldBuyWithControlledFailure: false;
property bool debugCheckoutSuccess: false;
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
property bool isWearable;
property string referrer;
property bool isInstalled;
// Style
color: hifi.colors.white;
Connections {
@ -85,7 +91,9 @@ Rectangle {
UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
} else {
root.itemHref = result.data.download_url;
root.isWearable = result.data.categories.indexOf("Wearables") > -1;
if (result.data.categories.indexOf("Wearables") > -1) {
root.itemType = "wearable";
}
root.activeView = "checkoutSuccess";
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
}
@ -97,32 +105,60 @@ Rectangle {
} else {
root.balanceReceived = true;
root.balanceAfterPurchase = result.data.balance - root.itemPrice;
root.setBuyText();
root.refreshBuyUI();
}
}
onInventoryResult: {
onAlreadyOwnedResult: {
if (result.status !== 'success') {
console.log("Failed to get purchases", result.data.message);
console.log("Failed to get Already Owned status", result.data.message);
} else {
root.purchasesReceived = true;
if (purchasesContains(result.data.assets, itemId)) {
root.alreadyOwned = true;
root.ownershipStatusReceived = true;
if (result.data.marketplace_item_id === root.itemId) {
root.alreadyOwned = result.data.already_owned;
} else {
console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!");
root.alreadyOwned = false;
}
root.setBuyText();
root.refreshBuyUI();
}
}
onAppInstalled: {
if (appHref === root.itemHref) {
root.isInstalled = true;
}
}
}
onItemIdChanged: {
Commerce.inventory();
root.ownershipStatusReceived = false;
Commerce.alreadyOwned(root.itemId);
itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
}
onItemHrefChanged: {
itemIsJson = root.itemHref.endsWith('.json');
if (root.itemHref.indexOf(".fst") > -1) {
root.itemType = "avatar";
} else if (root.itemHref.indexOf('.json.gz') > -1) {
root.itemType = "contentSet";
} else if (root.itemHref.indexOf('.app.json') > -1) {
root.itemType = "app";
} else if (root.itemHref.indexOf('.json') > -1) {
root.itemType = "entity"; // "wearable" type handled later
} else {
console.log("WARNING - Item type is UNKNOWN!");
root.itemType = "entity";
}
}
onItemTypeChanged: {
if (root.itemType === "entity" || root.itemType === "wearable" ||
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
root.isCertified = true;
} else {
root.isCertified = false;
}
}
onItemPriceChanged: {
@ -203,7 +239,7 @@ Rectangle {
color: hifi.colors.white;
Component.onCompleted: {
purchasesReceived = false;
ownershipStatusReceived = false;
balanceReceived = false;
Commerce.getWalletStatus();
}
@ -278,6 +314,32 @@ Rectangle {
anchors.left: parent.left;
anchors.right: parent.right;
Rectangle {
id: loading;
z: 997;
visible: !root.ownershipStatusReceived || !root.balanceReceived;
anchors.fill: parent;
color: hifi.colors.white;
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup/section.
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
propagateComposedEvents: false;
}
AnimatedImage {
id: loadingImage;
source: "../common/images/loader-blue.gif"
width: 74;
height: width;
anchors.verticalCenter: parent.verticalCenter;
anchors.horizontalCenter: parent.horizontalCenter;
}
}
RalewayRegular {
id: confirmPurchaseText;
anchors.top: parent.top;
@ -286,8 +348,8 @@ Rectangle {
anchors.leftMargin: 16;
width: paintedWidth;
height: paintedHeight;
text: "Confirm Purchase:";
color: hifi.colors.baseGray;
text: "Review Purchase:";
color: hifi.colors.black;
size: 28;
}
@ -400,7 +462,7 @@ Rectangle {
width: root.width;
// Anchors
anchors.top: separator2.bottom;
anchors.topMargin: 16;
anchors.topMargin: 0;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
@ -411,8 +473,8 @@ Rectangle {
Rectangle {
id: buyTextContainer;
visible: buyText.text !== "";
anchors.top: cancelPurchaseButton.bottom;
anchors.topMargin: 16;
anchors.top: parent.top;
anchors.topMargin: 10;
anchors.left: parent.left;
anchors.right: parent.right;
height: buyText.height + 30;
@ -454,32 +516,63 @@ Rectangle {
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
}
onLinkActivated: {
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
}
// "View in My Purchases" button
HifiControlsUit.Button {
id: viewInMyPurchasesButton;
visible: false;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top;
anchors.topMargin: 10;
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: "VIEW THIS ITEM IN MY PURCHASES";
onClicked: {
sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
}
}
// "Buy" button
HifiControlsUit.Button {
id: buyButton;
enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson;
color: hifi.buttons.blue;
visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible)
enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified);
color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: checkoutActionButtonsContainer.top;
anchors.topMargin: 16;
height: 40;
anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom :
(buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top);
anchors.topMargin: 10;
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item");
text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ?
(viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item");
onClicked: {
if (itemIsJson) {
buyButton.enabled = false;
if (root.isCertified) {
if (!root.shouldBuyWithControlledFailure) {
Commerce.buy(itemId, itemPrice);
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
lightboxPopup.titleText = "Purchase Content Set";
lightboxPopup.bodyText = "You will not be able to replace this domain's content with <b>" + root.itemName +
" </b>until the server owner gives you 'Replace Content' permissions.<br><br>Are you sure you want to purchase this content set?";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.buy('" + root.itemId + "', " + root.itemPrice + ");" +
"root.visible = false; buyButton.enabled = false; loading.visible = true;";
lightboxPopup.visible = true;
} else {
buyButton.enabled = false;
loading.visible = true;
Commerce.buy(root.itemId, root.itemPrice);
}
} else {
Commerce.buy(itemId, itemPrice, true);
buyButton.enabled = false;
loading.visible = true;
Commerce.buy(root.itemId, root.itemPrice, true);
}
} else {
if (urlHandler.canHandleUrl(itemHref)) {
@ -494,9 +587,9 @@ Rectangle {
id: cancelPurchaseButton;
color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.light;
anchors.top: buyButton.bottom;
anchors.topMargin: 16;
height: 40;
anchors.top: buyButton.visible ? buyButton.bottom : viewInMyPurchasesButton.bottom;
anchors.topMargin: 10;
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: "Cancel"
@ -522,31 +615,32 @@ Rectangle {
anchors.top: titleBarContainer.bottom;
anchors.bottom: root.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.leftMargin: 20;
anchors.right: parent.right;
anchors.rightMargin: 16;
anchors.rightMargin: 20;
RalewayRegular {
id: completeText;
anchors.top: parent.top;
anchors.topMargin: 30;
anchors.topMargin: 18;
anchors.left: parent.left;
width: paintedWidth;
height: paintedHeight;
text: "Thank you for your order!";
color: hifi.colors.baseGray;
size: 28;
size: 36;
}
RalewaySemiBold {
id: completeText2;
text: "The item " + '<font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
" has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] +
' <font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
" has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
// Text size
size: 20;
size: 18;
// Anchors
anchors.top: completeText.bottom;
anchors.topMargin: 10;
anchors.topMargin: 15;
height: paintedHeight;
anchors.left: parent.left;
anchors.right: parent.right;
@ -576,7 +670,7 @@ Rectangle {
RalewayBold {
anchors.fill: parent;
text: "REZZED";
text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
size: 18;
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
@ -592,26 +686,58 @@ Rectangle {
// "Rez" button
HifiControlsUit.Button {
id: rezNowButton;
enabled: root.canRezCertifiedItems || root.isWearable;
buttonGlyph: hifi.glyphs.lightning;
enabled: (root.itemType === "entity" && root.canRezCertifiedItems) ||
(root.itemType === "contentSet" && Entities.canReplaceContent()) ||
root.itemType === "wearable" || root.itemType === "avatar" || root.itemType === "app";
buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.light;
anchors.top: completeText2.bottom;
anchors.topMargin: 30;
anchors.topMargin: 27;
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: root.isWearable ? "Wear It" : "Rez It"
text: root.itemType === "app" && root.isInstalled ? "OPEN APP" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
onClicked: {
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear");
if (root.itemType === "contentSet") {
lightboxPopup.titleText = "Replace Content";
lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " +
"If you want to save the state of the content in this domain, create a backup before proceeding.<br><br>" +
"For more information about backing up and restoring content, " +
"<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
"click here to open info on your desktop browser.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" +
"root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" +
"UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');";
lightboxPopup.visible = true;
} else if (root.itemType === "avatar") {
lightboxPopup.titleText = "Change Avatar";
lightboxPopup.bodyText = "This will change your current avatar to " + root.itemName + " while retaining your wearables.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (root.itemType === "app") {
if (root.isInstalled) {
Commerce.openApp(root.itemHref);
} else {
Commerce.installApp(root.itemHref);
}
} else {
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.itemType);
}
}
}
RalewaySemiBold {
id: noPermissionText;
visible: !root.canRezCertifiedItems && !root.isWearable;
visible: !root.canRezCertifiedItems && root.itemType === "entity";
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">You do not have Certified Rez permissions in this domain.</a></font>'
// Text size
size: 16;
@ -640,7 +766,7 @@ Rectangle {
}
RalewaySemiBold {
id: explainRezText;
visible: !root.isWearable;
visible: root.itemType === "entity";
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">What does "Rez" mean?</a></font>'
// Text size
size: 16;
@ -663,9 +789,9 @@ Rectangle {
RalewaySemiBold {
id: myPurchasesLink;
text: '<font color="' + hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font>';
text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View this item in My Purchases</a></font>';
// Text size
size: 20;
size: 18;
// Anchors
anchors.top: explainRezText.visible ? explainRezText.bottom : (noPermissionText.visible ? noPermissionText.bottom : rezNowButton.bottom);
anchors.topMargin: 40;
@ -685,12 +811,12 @@ Rectangle {
RalewaySemiBold {
id: walletLink;
text: '<font color="' + hifi.colors.blueAccent + '"><a href="#">View receipt in Wallet</a></font>';
text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View receipt in Wallet</a></font>';
// Text size
size: 20;
size: 18;
// Anchors
anchors.top: myPurchasesLink.bottom;
anchors.topMargin: 20;
anchors.topMargin: 16;
height: paintedHeight;
anchors.left: parent.left;
anchors.right: parent.right;
@ -708,12 +834,12 @@ Rectangle {
RalewayRegular {
id: pendingText;
text: 'Your item is marked "pending" while your purchase is being confirmed. ' +
'<font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font>';
'<b><font color="' + hifi.colors.primaryHighlight + '"><a href="#">Learn More</a></font></b>';
// Text size
size: 20;
size: 18;
// Anchors
anchors.top: walletLink.bottom;
anchors.topMargin: 60;
anchors.topMargin: 32;
height: paintedHeight;
anchors.left: parent.left;
anchors.right: parent.right;
@ -739,11 +865,10 @@ Rectangle {
color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.light;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 20;
anchors.bottomMargin: 54;
anchors.right: parent.right;
anchors.rightMargin: 14;
width: parent.width/2 - anchors.rightMargin;
height: 60;
width: 193;
height: 44;
text: "Continue Shopping";
onClicked: {
sendToScript({method: 'checkout_continueShopping', itemId: itemId});
@ -851,7 +976,7 @@ Rectangle {
buyButton.color = hifi.buttons.red;
root.shouldBuyWithControlledFailure = true;
} else {
buyButton.text = (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
buyButton.color = hifi.buttons.blue;
root.shouldBuyWithControlledFailure = false;
}
@ -883,7 +1008,7 @@ Rectangle {
itemHref = message.params.itemHref;
referrer = message.params.referrer;
itemAuthor = message.params.itemAuthor;
setBuyText();
refreshBuyUI();
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
@ -891,22 +1016,13 @@ Rectangle {
}
signal sendToScript(var message);
function purchasesContains(purchasesJson, id) {
for (var idx = 0; idx < purchasesJson.length; idx++) {
if(purchasesJson[idx].id === id) {
return true;
}
}
return false;
}
function setBuyText() {
if (root.itemIsJson) {
if (root.purchasesReceived && root.balanceReceived) {
function refreshBuyUI() {
if (root.isCertified) {
if (root.ownershipStatusReceived && root.balanceReceived) {
if (root.balanceAfterPurchase < 0) {
if (root.alreadyOwned) {
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.<br>" +
'<font color="' + hifi.colors.blueAccent + '"><a href="#">View the copy you own in My Purchases</a></font></b>';
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
viewInMyPurchasesButton.visible = true;
} else {
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
}
@ -916,15 +1032,19 @@ Rectangle {
buyGlyph.size = 54;
} else {
if (root.alreadyOwned) {
buyText.text = '<b>You already own this item.<br>Purchasing it will buy another copy.<br><font color="'
+ hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font></b>';
buyTextContainer.color = "#FFD6AD";
buyTextContainer.border.color = "#FAC07D";
buyGlyph.text = hifi.glyphs.alert;
buyGlyph.size = 46;
viewInMyPurchasesButton.visible = true;
} else {
buyText.text = "";
}
if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " +
"<b>domain's server settings</b> before you can replace this domain's content with <b>" + root.itemName + "</b>";
buyTextContainer.color = "#FFC3CD";
buyTextContainer.border.color = "#F3808F";
buyGlyph.text = hifi.glyphs.alert;
buyGlyph.size = 54;
}
}
} else {
buyText.text = "";
@ -945,8 +1065,8 @@ Rectangle {
root.activeView = "checkoutSuccess";
}
root.balanceReceived = false;
root.purchasesReceived = false;
Commerce.inventory();
root.ownershipStatusReceived = false;
Commerce.alreadyOwned(root.itemId);
Commerce.balance();
}

View file

@ -100,6 +100,10 @@ Rectangle {
size: 20;
verticalAlignment: Text.AlignTop;
wrapMode: Text.WordWrap;
onLinkActivated: {
sendToParent({ method: 'commerceLightboxLinkClicked', linkUrl: link });
}
}
Item {

View file

@ -90,11 +90,11 @@ Rectangle {
id: introText2;
text: "My Purchases";
// Text size
size: 28;
size: 22;
// Anchors
anchors.top: introText1.bottom;
anchors.left: parent.left;
anchors.leftMargin: 12;
anchors.leftMargin: 24;
anchors.right: parent.right;
height: paintedHeight;
// Style

Binary file not shown.

After

(image error) Size: 58 KiB

View file

@ -208,6 +208,7 @@ Rectangle {
// able to click on a button/mouseArea underneath the popup/section.
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
propagateComposedEvents: false;
}

View file

@ -20,6 +20,7 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../wallet" as HifiWallet
import TabletScriptingInterface 1.0
// references XXX from root context
@ -29,7 +30,6 @@ Item {
id: root;
property string purchaseStatus;
property bool purchaseStatusChanged;
property bool canRezCertifiedItems: false;
property string itemName;
property string itemId;
property string itemPreviewImageUrl;
@ -39,7 +39,15 @@ Item {
property int itemEdition;
property int numberSold;
property int limitedRun;
property bool isWearable;
property string itemType;
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE", "INSTALL", "WEAR"];
property var buttonTextClicked: ["REZZED", "WORN", "REPLACED", "INSTALLED", "WORN"]
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
property bool showConfirmation: false;
property bool hasPermissionToRezThis;
property bool permissionExplanationCardVisible;
property bool isInstalled;
property string originalStatusText;
property string originalStatusColor;
@ -47,6 +55,47 @@ Item {
height: 110;
width: parent.width;
Connections {
target: Commerce;
onContentSetChanged: {
if (contentSetHref === root.itemHref) {
showConfirmation = true;
}
}
onAppInstalled: {
if (appHref === root.itemHref) {
root.isInstalled = true;
}
}
onAppUninstalled: {
if (appHref === root.itemHref) {
root.isInstalled = false;
}
}
}
Connections {
target: MyAvatar;
onSkeletonModelURLChanged: {
if (skeletonModelURL === root.itemHref) {
showConfirmation = true;
}
}
}
onItemTypeChanged: {
if ((itemType === "entity" && (!Entities.canRezCertified() && !Entities.canRezTmpCertified())) ||
(itemType === "contentSet" && !Entities.canReplaceContent())) {
root.hasPermissionToRezThis = false;
} else {
root.hasPermissionToRezThis = true;
}
}
onPurchaseStatusChangedChanged: {
if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
root.originalStatusText = statusText.text;
@ -57,6 +106,15 @@ Item {
}
}
onShowConfirmationChanged: {
if (root.showConfirmation) {
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.itemType);
root.showConfirmation = false;
}
}
Timer {
id: confirmedTimer;
interval: 3000;
@ -73,10 +131,10 @@ Item {
color: hifi.colors.white;
// Size
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.top: parent.top;
anchors.rightMargin: 16;
anchors.verticalCenter: parent.verticalCenter;
height: root.height - 10;
Image {
@ -96,15 +154,20 @@ Item {
}
}
TextMetrics {
id: itemNameTextMetrics;
font: itemName.font;
text: itemName.text;
}
RalewaySemiBold {
id: itemName;
anchors.top: itemPreviewImage.top;
anchors.topMargin: 4;
anchors.left: itemPreviewImage.right;
anchors.leftMargin: 8;
anchors.right: buttonContainer.left;
anchors.rightMargin: 8;
width: !noPermissionGlyph.visible ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) :
Math.min(itemNameTextMetrics.tightBoundingRect.width + 2,
buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2);
height: paintedHeight;
// Text size
size: 24;
@ -130,6 +193,93 @@ Item {
}
}
}
HiFiGlyphs {
id: noPermissionGlyph;
visible: !root.hasPermissionToRezThis;
anchors.verticalCenter: itemName.verticalCenter;
anchors.left: itemName.right;
anchors.leftMargin: itemName.truncated ? -10 : -2;
text: hifi.glyphs.info;
// Size
size: 40;
width: 32;
// Style
color: hifi.colors.redAccent;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
noPermissionGlyph.color = hifi.colors.redHighlight;
}
onExited: {
noPermissionGlyph.color = hifi.colors.redAccent;
}
onClicked: {
root.sendToPurchases({ method: 'openPermissionExplanationCard' });
}
}
}
Rectangle {
id: permissionExplanationCard;
z: 995;
visible: root.permissionExplanationCardVisible;
anchors.fill: parent;
color: hifi.colors.white;
RalewayRegular {
id: permissionExplanationText;
text: {
if (root.itemType === "contentSet") {
"You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
} else if (root.itemType === "entity") {
"You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
} else {
""
}
}
size: 16;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: permissionExplanationGlyph.left;
color: hifi.colors.baseGray;
wrapMode: Text.WordWrap;
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
}
}
// "Close" button
HiFiGlyphs {
id: permissionExplanationGlyph;
text: hifi.glyphs.close;
color: hifi.colors.baseGray;
size: 26;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
width: 77;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
parent.text = hifi.glyphs.closeInverted;
}
onExited: {
parent.text = hifi.glyphs.close;
}
onClicked: {
root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true });
}
}
}
}
Item {
id: certificateContainer;
@ -151,19 +301,19 @@ Item {
anchors.bottom: parent.bottom;
width: 32;
// Style
color: hifi.colors.lightGray;
color: hifi.colors.black;
}
RalewayRegular {
id: viewCertificateText;
text: "VIEW CERTIFICATE";
size: 14;
size: 13;
anchors.left: certificateIcon.right;
anchors.leftMargin: 4;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
color: hifi.colors.lightGray;
color: hifi.colors.black;
}
MouseArea {
@ -173,13 +323,13 @@ Item {
sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId});
}
onEntered: {
certificateIcon.color = hifi.colors.black;
viewCertificateText.color = hifi.colors.black;
}
onExited: {
certificateIcon.color = hifi.colors.lightGray;
viewCertificateText.color = hifi.colors.lightGray;
}
onExited: {
certificateIcon.color = hifi.colors.black;
viewCertificateText.color = hifi.colors.black;
}
}
}
@ -193,14 +343,14 @@ Item {
anchors.right: buttonContainer.left;
anchors.rightMargin: 2;
FiraSansRegular {
RalewayRegular {
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
width: paintedWidth;
text: "#" + root.itemEdition;
size: 15;
color: "#cc6a6a6a";
size: 13;
color: hifi.colors.black;
verticalAlignment: Text.AlignTop;
}
}
@ -311,7 +461,7 @@ Item {
id: rezzedNotifContainer;
z: 998;
visible: false;
color: hifi.colors.blueHighlight;
color: "#1FC6A6";
anchors.fill: buttonContainer;
MouseArea {
anchors.fill: parent;
@ -321,8 +471,8 @@ Item {
RalewayBold {
anchors.fill: parent;
text: "REZZED";
size: 18;
text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
size: 15;
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
horizontalAlignment: Text.AlignHCenter;
@ -335,27 +485,89 @@ Item {
}
}
Rectangle {
id: appButtonContainer;
color: hifi.colors.white;
z: 994;
visible: root.isInstalled;
anchors.fill: buttonContainer;
HifiControlsUit.Button {
id: openAppButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.right: parent.right;
anchors.left: parent.left;
width: 92;
height: 44;
text: "OPEN"
onClicked: {
Commerce.openApp(root.itemHref);
}
}
HifiControlsUit.Button {
id: uninstallAppButton;
color: hifi.buttons.noneBorderless;
colorScheme: hifi.colorSchemes.light;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
anchors.left: parent.left;
height: 44;
text: "UNINSTALL"
onClicked: {
Commerce.uninstallApp(root.itemHref);
}
}
}
Button {
id: buttonContainer;
property int color: hifi.buttons.red;
property int color: hifi.buttons.blue;
property int colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
anchors.right: parent.right;
anchors.rightMargin: 4;
width: height;
enabled: (root.canRezCertifiedItems || root.isWearable) && root.purchaseStatus !== "invalidated";
enabled: root.hasPermissionToRezThis &&
root.purchaseStatus !== "invalidated" &&
MyAvatar.skeletonModelURL !== root.itemHref;
onHoveredChanged: {
if (hovered) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onFocusChanged: {
if (focus) {
Tablet.playSound(TabletEnums.ButtonHover);
}
}
onClicked: {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.isWearable ? "rez" : "wear");
Tablet.playSound(TabletEnums.ButtonClick);
if (root.itemType === "contentSet") {
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref});
} else if (root.itemType === "avatar") {
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
} else if (root.itemType === "app") {
// "Run" and "Uninstall" buttons are separate.
Commerce.installApp(root.itemHref);
} else {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
root.showConfirmation = true;
}
}
style: ButtonStyle {
background: Rectangle {
radius: 4;
gradient: Gradient {
GradientStop {
position: 0.2
@ -390,13 +602,13 @@ Item {
label: Item {
HiFiGlyphs {
id: lightningIcon;
text: hifi.glyphs.lightning;
id: rezIcon;
text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
// Size
size: 32;
size: 60;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 12;
anchors.topMargin: 0;
anchors.left: parent.left;
anchors.right: parent.right;
horizontalAlignment: Text.AlignHCenter;
@ -405,18 +617,19 @@ Item {
: hifi.buttons.disabledTextColor[control.colorScheme]
}
RalewayBold {
anchors.top: lightningIcon.bottom;
anchors.topMargin: -20;
id: rezIconLabel;
anchors.top: rezIcon.bottom;
anchors.topMargin: -4;
anchors.right: parent.right;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
size: 16;
size: 15;
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: root.isWearable ? "Wear It" : "Rez It"
text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
}
}
}
@ -425,11 +638,11 @@ Item {
DropShadow {
anchors.fill: mainContainer;
horizontalOffset: 3;
verticalOffset: 3;
radius: 8.0;
samples: 17;
color: "#80000000";
horizontalOffset: 0;
verticalOffset: 4;
radius: 4.0;
samples: 9
color: Qt.rgba(0, 0, 0, 0.25);
source: mainContainer;
}

View file

@ -32,11 +32,11 @@ Rectangle {
property bool securityImageResultReceived: false;
property bool purchasesReceived: false;
property bool punctuationMode: false;
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
property bool pendingInventoryReply: true;
property bool isShowingMyItems: false;
property bool isDebuggingFirstUseTutorial: false;
property int pendingItemCount: 0;
property string installedApps;
// Style
color: hifi.colors.white;
Connections {
@ -62,6 +62,7 @@ Rectangle {
root.activeView = "firstUseTutorial";
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
Commerce.inventory();
}
} else {
@ -148,7 +149,11 @@ Rectangle {
Connections {
onSendToParent: {
sendToScript(msg);
if (msg.method === 'commerceLightboxLinkClicked') {
Qt.openUrlExternally(msg.linkUrl);
} else {
sendToScript(msg);
}
}
}
}
@ -266,6 +271,7 @@ Rectangle {
case 'tutorial_finished':
Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
Commerce.inventory();
break;
}
@ -297,7 +303,7 @@ Rectangle {
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 12;
anchors.rightMargin: 16;
anchors.top: parent.top;
anchors.topMargin: 4;
@ -308,11 +314,11 @@ Rectangle {
anchors.bottom: parent.bottom;
anchors.bottomMargin: 10;
anchors.left: parent.left;
anchors.leftMargin: 4;
anchors.leftMargin: 16;
width: paintedWidth;
text: isShowingMyItems ? "My Items" : "My Purchases";
color: hifi.colors.baseGray;
size: 28;
color: hifi.colors.black;
size: 22;
}
HifiControlsUit.TextField {
@ -323,8 +329,8 @@ Rectangle {
hasRoundedBorder: true;
anchors.left: myText.right;
anchors.leftMargin: 16;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
height: 39;
anchors.verticalCenter: parent.verticalCenter;
anchors.right: parent.right;
placeholderText: "filter items";
@ -345,7 +351,7 @@ Rectangle {
HifiControlsUit.Separator {
id: separator;
colorScheme: 1;
colorScheme: 2;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: filterBarContainer.bottom;
@ -365,69 +371,6 @@ Rectangle {
id: filteredPurchasesModel;
}
Rectangle {
id: cantRezCertified;
visible: !root.canRezCertifiedItems;
color: "#FFC3CD";
radius: 4;
border.color: hifi.colors.redAccent;
border.width: 1;
anchors.top: separator.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 80;
HiFiGlyphs {
id: lightningIcon;
text: hifi.glyphs.lightning;
// Size
size: 36;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 18;
anchors.left: parent.left;
anchors.leftMargin: 12;
horizontalAlignment: Text.AlignHCenter;
// Style
color: hifi.colors.lightGray;
}
RalewayRegular {
text: "You don't have permission to rez certified items in this domain. " +
'<b><font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font></b>';
// Text size
size: 18;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.left: lightningIcon.right;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
// Style
color: hifi.colors.baseGray;
wrapMode: Text.WordWrap;
// Alignment
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
lightboxPopup.titleText = "Rez Permission Required";
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "OPEN GOTO";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
lightboxPopup.visible = true;
}
}
}
ListView {
id: purchasesContentsList;
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
@ -436,13 +379,12 @@ Rectangle {
snapMode: ListView.SnapToItem;
highlightRangeMode: ListView.StrictlyEnforceRange;
// Anchors
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
anchors.top: separator.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width;
delegate: PurchasedItem {
canRezCertifiedItems: root.canRezCertifiedItems;
itemName: title;
itemId: id;
itemPreviewImageUrl: preview;
@ -454,16 +396,32 @@ Rectangle {
numberSold: model.number_sold;
limitedRun: model.limited_run;
displayedItemCount: model.displayedItemCount;
isWearable: model.categories.indexOf("Wearables") > -1;
anchors.topMargin: 12;
anchors.bottomMargin: 12;
permissionExplanationCardVisible: model.permissionExplanationCardVisible;
isInstalled: model.isInstalled;
itemType: {
if (model.root_file_url.indexOf(".fst") > -1) {
"avatar";
} else if (model.categories.indexOf("Wearables") > -1) {
"wearable";
} else if (model.root_file_url.endsWith('.json.gz')) {
"contentSet";
} else if (model.root_file_url.endsWith('.app.json')) {
"app";
} else if (model.root_file_url.endsWith('.json')) {
"entity";
} else {
"unknown";
}
}
anchors.topMargin: 10;
anchors.bottomMargin: 10;
Connections {
onSendToPurchases: {
if (msg.method === 'purchases_itemInfoClicked') {
sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
} else if (msg.method === "purchases_rezClicked") {
sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, isWearable: isWearable});
sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, itemType: itemType});
} else if (msg.method === 'purchases_itemCertificateClicked') {
inspectionCertificate.visible = true;
inspectionCertificate.isLightbox = true;
@ -482,8 +440,51 @@ Rectangle {
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.visible = true;
} else if (msg.method === "showReplaceContentLightbox") {
lightboxPopup.titleText = "Replace Content";
lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " +
"If you want to save the state of the content in this domain, create a backup before proceeding.<br><br>" +
"For more information about backing up and restoring content, " +
"<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
"click here to open info on your desktop browser.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (msg.method === "showChangeAvatarLightbox") {
lightboxPopup.titleText = "Change Avatar";
lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + msg.itemHref + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (msg.method === "showPermissionsExplanation") {
if (msg.itemType === "entity") {
lightboxPopup.titleText = "Rez Certified Permission";
lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
"Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
lightboxPopup.button2text = "OPEN GOTO";
lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
} else if (msg.itemType === "contentSet") {
lightboxPopup.titleText = "Replace Content Permission";
lightboxPopup.bodyText = "You do not have the permission 'Replace Content' in this <b>domain's server settings</b>. The domain owner " +
"must enable it for you before you can replace content sets in this domain.";
}
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.visible = true;
} else if (msg.method === "setFilterText") {
filterBar.text = msg.filterText;
} else if (msg.method === "openPermissionExplanationCard") {
for (var i = 0; i < filteredPurchasesModel.count; i++) {
if (i !== index || msg.closeAll) {
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", false);
} else {
filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true);
}
}
}
}
}
@ -683,8 +684,13 @@ Rectangle {
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) {
filteredPurchasesModel.clear();
var currentId;
for (var i = 0; i < tempPurchasesModel.count; i++) {
currentId = tempPurchasesModel.get(i).id;
filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
}
populateDisplayedItemCounts();

View file

@ -75,8 +75,6 @@ Item {
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
passphraseField.error = false;
passphraseField.focus = true;
sendSignalToParent({method: 'disableHmdPreview'});
} else {
sendSignalToParent({method: 'maybeEnableHmdPreview'});
@ -206,6 +204,14 @@ Item {
placeholderText: "passphrase";
activeFocusOnPress: true;
activeFocusOnTab: true;
onVisibleChanged: {
if (visible) {
error = false;
focus = true;
forceActiveFocus();
}
}
onFocusChanged: {
root.keyboardRaised = focus;

View file

@ -923,7 +923,7 @@ Item {
anchors.leftMargin: 20;
anchors.right: parent.right;
anchors.rightMargin: 20;
height: 140;
height: 95;
FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; }
TextArea {
@ -947,8 +947,14 @@ Item {
wrapMode: TextEdit.Wrap;
activeFocusOnPress: true;
activeFocusOnTab: true;
// Workaround for no max length on TextAreas
onTextChanged: {
// Don't allow tabs or newlines
if ((/[\n\r\t]/g).test(text)) {
var cursor = cursorPosition;
text = text.replace(/[\n\r\t]/g, '');
cursorPosition = cursor-1;
}
// Workaround for no max length on TextAreas
if (text.length > maximumLength) {
var cursor = cursorPosition;
text = previousText;

View file

@ -199,7 +199,7 @@ Rectangle {
var SHAPE_TYPE_BOX = 4;
var SHAPE_TYPE_SPHERE = 5;
var SHAPE_TYPES = [];ww
var SHAPE_TYPES = [];
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls 2.2 // Need both for short-term fix
import QtWebEngine 1.1
import QtWebChannel 1.0
import QtQuick.Controls.Styles 1.4
@ -10,6 +11,7 @@ import "../../controls-uit" as HifiControls
import "../../styles-uit"
TabView {
id: editTabView
// anchors.fill: parent
@ -23,8 +25,27 @@ TabView {
Rectangle {
color: "#404040"
id: container
Flickable {
height: parent.height
width: parent.width
contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
assetServerButton.anchors.topMargin + importButton.anchors.topMargin
contentWidth: width
ScrollBar.vertical : ScrollBar {
visible: parent.contentHeight > parent.height
width: 20
background: Rectangle {
color: hifi.colors.tableScrollBackgroundDark
}
}
Text {
id: header
color: "#ffffff"
text: "Choose an Entity Type to Create:"
font.pixelSize: 14
@ -176,6 +197,7 @@ TabView {
}
HifiControls.Button {
id: importButton
text: "Import Entities (.json)"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
@ -192,6 +214,7 @@ TabView {
}
}
}
} // Flickable
}
Tab {

View file

@ -131,7 +131,7 @@ Rectangle {
spacing: 5
anchors.horizontalCenter: column3.horizontalCenter
anchors.horizontalCenterOffset: -20
anchors.horizontalCenterOffset: 0
Button {
id: button1

View file

@ -130,6 +130,7 @@ Item {
flickableDirection: Flickable.AutoFlickIfNeeded
keyNavigationEnabled: false
highlightFollowsCurrentItem: false
interactive: false
property int previousGridIndex: -1

View file

@ -218,7 +218,7 @@ Item {
readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ]
readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight]
readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow]
@ -354,5 +354,9 @@ Item {
readonly property string wallet: "\ue027"
readonly property string paperPlane: "\ue028"
readonly property string passphrase: "\ue029"
readonly property string globe: "\ue02c"
readonly property string wand: "\ue02d"
readonly property string hat: "\ue02e"
readonly property string install: "\ue02f"
}
}

View file

@ -68,7 +68,6 @@
#include <Midi.h>
#include <AudioInjectorManager.h>
#include <AvatarBookmarks.h>
#include <AvatarEntitiesBookmarks.h>
#include <CursorManager.h>
#include <DebugDraw.h>
#include <DeferredLightingEffect.h>
@ -392,7 +391,7 @@ const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensi
class DeadlockWatchdogThread : public QThread {
public:
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND;
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 120 * USECS_PER_SECOND; // 2 mins with no checkin probably a deadlock
static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large
static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples
@ -785,7 +784,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<GooglePolyScriptingInterface>();
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
DependencyManager::set<AvatarBookmarks>();
DependencyManager::set<AvatarEntitiesBookmarks>();
DependencyManager::set<LocationBookmarks>();
DependencyManager::set<Snapshot>();
DependencyManager::set<CloseEventSender>();
@ -1719,6 +1717,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection();
properties["hardware_stats"] = displayPlugin->getHardwareStats();
// deadlock watchdog related stats
properties["deadlock_watchdog_maxElapsed"] = (int)DeadlockWatchdogThread::_maxElapsed;
properties["deadlock_watchdog_maxElapsedAverage"] = (int)DeadlockWatchdogThread::_maxElapsedAverage;
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond();
properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond();
@ -2610,7 +2612,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
// Caches
@ -4836,7 +4837,6 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
if (_keyboardFocusHighlightID == UNKNOWN_OVERLAY_ID || !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);
@ -5978,7 +5978,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(toolbarScriptingInterface);
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine.data(), CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue);
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter, "Window");
// register `location` on the global object.
@ -5998,7 +5997,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get<AudioScope>().data());
scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
@ -6295,6 +6293,24 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
return true;
}
void Application::replaceDomainContent(const QString& url) {
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
}
bool Application::askToReplaceDomainContent(const QString& url) {
QString methodDetails;
const int MAX_CHARACTERS_PER_LINE = 90;
@ -6314,21 +6330,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
QString details;
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
// Given confirmation, send request to domain server to replace content
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
replaceDomainContent(url);
details = "SuccessfulRequestToReplaceContent";
} else {
details = "UserDeclinedToReplaceContent";
@ -7549,6 +7551,18 @@ void Application::deadlockApplication() {
}
}
// cause main thread to be unresponsive for 35 seconds
void Application::unresponsiveApplication() {
// to avoid compiler warnings about a loop that will never exit
uint64_t start = usecTimestampNow();
uint64_t UNRESPONSIVE_FOR_SECONDS = 35;
uint64_t UNRESPONSIVE_FOR_USECS = UNRESPONSIVE_FOR_SECONDS * USECS_PER_SECOND;
qCDebug(interfaceapp) << "Intentionally cause Interface to be unresponsive for " << UNRESPONSIVE_FOR_SECONDS << " seconds";
while (usecTimestampNow() - start < UNRESPONSIVE_FOR_USECS) {
QThread::sleep(1);
}
}
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {

View file

@ -268,9 +268,8 @@ public:
float getGameLoopRate() const { return _gameLoopCounter.rate(); }
// Note that takeSnapshot has a default value, as this method is used internally.
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
void takeSecondaryCameraSnapshot(const QString& filename);
void takeSecondaryCameraSnapshot(const QString& filename = QString());
void shareSnapshot(const QString& filename, const QUrl& href = QUrl(""));
@ -288,6 +287,8 @@ public:
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
void saveNextPhysicsStats(QString filename);
void replaceDomainContent(const QString& url);
signals:
void svoImportRequested(const QString& url);
@ -369,6 +370,7 @@ public slots:
void updateHeartbeat() const;
static void deadlockApplication();
static void unresponsiveApplication(); // cause main thread to be unresponsive for 35 seconds
void rotationModeChanged() const;

View file

@ -19,6 +19,11 @@
#include <Application.h>
#include <OffscreenUi.h>
#include <avatar/AvatarManager.h>
#include <EntityItemID.h>
#include <EntityTree.h>
#include <PhysicalEntitySimulation.h>
#include <EntityEditPacketSender.h>
#include <VariantMapToScriptValue.h>
#include "MainWindow.h"
#include "Menu.h"
@ -29,6 +34,62 @@
#include <QtQuick/QQuickWindow>
void addAvatarEntities(const QVariantList& avatarEntities) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
if (!entityTree) {
return;
}
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
QScriptEngine scriptEngine;
for (int index = 0; index < avatarEntities.count(); index++) {
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
QVariant variantProperties = avatarEntityProperties["properties"];
QVariantMap asMap = variantProperties.toMap();
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties entityProperties;
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
entityProperties.setParentID(myNodeID);
entityProperties.setClientOnly(true);
entityProperties.setOwningAvatarID(myNodeID);
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
entityProperties.markAllChanged();
EntityItemID id = EntityItemID(QUuid::createUuid());
bool success = true;
entityTree->withWriteLock([&] {
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
if (entity) {
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
bool success;
AACube queryAACube = entity->getQueryAACube(success);
if (success) {
entityProperties.setQueryAACube(queryAACube);
}
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
entityProperties.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
success = false;
}
});
if (success) {
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
}
}
}
AvatarBookmarks::AvatarBookmarks() {
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
readFromFile();
@ -38,7 +99,7 @@ void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
QFile oldConfig(oldConfigPath);
// I imagine that in a year from now, this code for migrating (as well as the two lines above)
// may be removed since all bookmarks should have been migrated by then
// - Robbie Uvanni (6.8.2017)
@ -48,9 +109,9 @@ void AvatarBookmarks::readFromFile() {
} else {
qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME;
}
}
Bookmarks::readFromFile();
}
Bookmarks::readFromFile();
}
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
@ -81,23 +142,27 @@ void AvatarBookmarks::changeToBookmarkedAvatar() {
myAvatar->useFullAvatarURL(action->data().toString());
qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark ";
} else {
const QMap<QString, QVariant> bookmark = action->data().toMap();
// Not magic value. This is the current made version, and if it changes this interpreter should be updated to
// Not magic value. This is the current made version, and if it changes this interpreter should be updated to
// handle the new one separately.
// This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous
if (bookmark.value(ENTRY_VERSION) == 3) {
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
myAvatar->removeAvatarEntities();
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
qCDebug(interfaceapp) << "Attach " << attachments;
myAvatar->setAttachmentsVariant(attachments);
qCDebug(interfaceapp) << "Attach " << attachments;
myAvatar->setAttachmentsVariant(attachments);
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(qScale);
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
addAvatarEntities(avatarEntities);
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(qScale);
} else {
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark";
}
@ -126,6 +191,7 @@ void AvatarBookmarks::addBookmark() {
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
});

View file

@ -34,6 +34,7 @@ private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
const QString ENTRY_AVATAR_URL = "avatarUrl";
const QString ENTRY_AVATAR_ATTACHMENTS = "attachments";
const QString ENTRY_AVATAR_ENTITIES = "avatarEntites";
const QString ENTRY_AVATAR_SCALE = "avatarScale";
const QString ENTRY_VERSION = "version";

View file

@ -1,168 +0,0 @@
//
// AvatarEntitiesBookmarks.cpp
// interface/src
//
// Created by Dante Ruiz on 15/01/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QAction>
#include <QInputDialog>
#include <QMessageBox>
#include <QStandardPaths>
#include <QQmlContext>
#include <QList>
#include <Application.h>
#include <OffscreenUi.h>
#include <EntityItemProperties.h>
#include <GLMHelpers.h>
#include <avatar/AvatarManager.h>
#include <EntityItemID.h>
#include <EntityTree.h>
#include <PhysicalEntitySimulation.h>
#include <EntityEditPacketSender.h>
#include <VariantMapToScriptValue.h>
#include "MainWindow.h"
#include "Menu.h"
#include "AvatarEntitiesBookmarks.h"
#include "InterfaceLogging.h"
#include "QVariantGLM.h"
#include <QtQuick/QQuickWindow>
void addAvatarEntities(const QVariantList& avatarEntities) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
if (!entityTree) {
return;
}
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
QScriptEngine scriptEngine;
for (int index = 0; index < avatarEntities.count(); index++) {
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
QVariant variantProperties = avatarEntityProperties["properties"];
QVariantMap asMap = variantProperties.toMap();
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties entityProperties;
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
entityProperties.setParentID(myNodeID);
entityProperties.setClientOnly(true);
entityProperties.setOwningAvatarID(myNodeID);
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
entityProperties.markAllChanged();
EntityItemID id = EntityItemID(QUuid::createUuid());
bool success = true;
entityTree->withWriteLock([&] {
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
if (entity) {
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
bool success;
AACube queryAACube = entity->getQueryAACube(success);
if (success) {
entityProperties.setQueryAACube(queryAACube);
}
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
entityProperties.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
success = false;
}
});
if (success) {
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
}
}
}
AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() {
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME;
Bookmarks::readFromFile();
}
void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities);
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
_bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks);
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark);
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
addBookmarkToMenu(menubar, it.key(), it.value());
}
Bookmarks::sortActions(menubar, _bookmarksMenu);
}
void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() {
QAction* action = qobject_cast<QAction*>(sender());
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QMap<QString, QVariant> bookmark = action->data().toMap();
if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) {
myAvatar->removeAvatarEntities();
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
myAvatar->useFullAvatarURL(avatarUrl);
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
addAvatarEntities(avatarEntities);
const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
myAvatar->setAvatarScale(avatarScale);
} else {
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark";
}
}
void AvatarEntitiesBookmarks::addBookmark() {
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "Name", QString());
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
auto bookmarkName = response.toString();
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
QVariantMap bookmark;
bookmark.insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION);
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
});
}
void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
QAction* changeAction = _bookmarksMenu->newAction();
changeAction->setData(bookmark);
connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities()));
if (!_isMenuSorted) {
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
} else {
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
Bookmarks::sortActions(menubar, _bookmarksMenu);
}
}

View file

@ -1,45 +0,0 @@
//
// AvatarEntitiesBookmarks.h
// interface/src
//
// Created by Dante Ruiz on 15/01/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AvatarEntitiesBookmarks_h
#define hifi_AvatarEntitiesBookmarks_h
#include <DependencyManager.h>
#include "Bookmarks.h"
class AvatarEntitiesBookmarks: public Bookmarks, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
AvatarEntitiesBookmarks();
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
public slots:
void addBookmark();
protected:
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
private:
const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json";
const QString ENTRY_AVATAR_URL = "AvatarUrl";
const QString ENTRY_AVATAR_SCALE = "AvatarScale";
const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities";
const QString ENTRY_VERSION = "version";
const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1;
private slots:
void applyBookmarkedAvatarEntities();
};
#endif

View file

@ -34,7 +34,6 @@
#include "audio/AudioScope.h"
#include "avatar/AvatarManager.h"
#include "AvatarBookmarks.h"
#include "AvatarEntitiesBookmarks.h"
#include "devices/DdeFaceTracker.h"
#include "MainWindow.h"
#include "render/DrawStatus.h"
@ -207,9 +206,6 @@ Menu::Menu() {
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
avatarBookmarks->setupMenus(this, avatarMenu);
auto avatarEntitiesBookmarks = DependencyManager::get<AvatarEntitiesBookmarks>();
avatarEntitiesBookmarks->setupMenus(this, avatarMenu);
// Display menu ----------------------------------
// FIXME - this is not yet matching Alan's spec because it doesn't have
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
@ -712,6 +708,7 @@ Menu::Menu() {
MenuWrapper* crashMenu = developerMenu->addMenu("Crash");
addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication()));
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction);
connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); });

View file

@ -46,7 +46,6 @@ namespace MenuOption {
const QString AutoMuteAudio = "Auto Mute Microphone";
const QString AvatarReceiveStats = "Show Receive Stats";
const QString AvatarBookmarks = "Avatar Bookmarks";
const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks";
const QString Back = "Back";
const QString BinaryEyelidControl = "Binary Eyelid Control";
const QString BookmarkAvatar = "Bookmark Avatar";
@ -77,6 +76,7 @@ namespace MenuOption {
const QString CrashNewFault = "New Fault";
const QString CrashNewFaultThreaded = "New Fault (threaded)";
const QString DeadlockInterface = "Deadlock Interface";
const QString UnresponsiveInterface = "Unresponsive Interface";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DefaultSkybox = "Default Skybox";
const QString DeleteAvatarBookmark = "Delete Avatar Bookmark...";

View file

@ -1479,7 +1479,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
});
saveAvatarUrl();
emit skeletonChanged();
emit skeletonModelURLChanged();
}
void MyAvatar::removeAvatarEntities() {

View file

@ -49,6 +49,7 @@ Handler(balance)
Handler(inventory)
Handler(transferHfcToNode)
Handler(transferHfcToUsername)
Handler(alreadyOwned)
void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) {
auto accountManager = DependencyManager::get<AccountManager>();
@ -336,3 +337,12 @@ void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& userna
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure");
}
void Ledger::alreadyOwned(const QString& marketplaceId) {
auto wallet = DependencyManager::get<Wallet>();
QString endpoint = "already_owned";
QJsonObject request;
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
request["marketplace_item_id"] = marketplaceId;
send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
}

View file

@ -35,6 +35,7 @@ public:
void certificateInfo(const QString& certificateId);
void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);
void alreadyOwned(const QString& marketplaceId);
enum CertificateStatus {
CERTIFICATE_STATUS_UNKNOWN = 0,
@ -55,6 +56,7 @@ signals:
void certificateInfoResult(QJsonObject result);
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
void alreadyOwnedResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
@ -79,6 +81,8 @@ public slots:
void transferHfcToNodeFailure(QNetworkReply& reply);
void transferHfcToUsernameSuccess(QNetworkReply& reply);
void transferHfcToUsernameFailure(QNetworkReply& reply);
void alreadyOwnedSuccess(QNetworkReply& reply);
void alreadyOwnedFailure(QNetworkReply& reply);
private:
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);

View file

@ -10,11 +10,17 @@
//
#include "QmlCommerce.h"
#include "CommerceLogging.h"
#include "Application.h"
#include "DependencyManager.h"
#include "Ledger.h"
#include "Wallet.h"
#include <AccountManager.h>
#include <Application.h>
#include <UserActivityLogger.h>
#include <ScriptEngines.h>
#include <ui/TabletScriptingInterface.h>
#include "scripting/HMDScriptingInterface.h"
QmlCommerce::QmlCommerce() {
auto ledger = DependencyManager::get<Ledger>();
@ -28,14 +34,18 @@ QmlCommerce::QmlCommerce() {
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
connect(ledger.data(), &Ledger::alreadyOwnedResult, this, &QmlCommerce::alreadyOwnedResult);
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
setPassphrase("");
});
_appsPath = PathUtils::getAppDataPath() + "Apps/";
}
void QmlCommerce::getWalletStatus() {
@ -163,3 +173,164 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou
QString key = keys[0];
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
}
void QmlCommerce::replaceContentSet(const QString& itemHref) {
qApp->replaceDomainContent(itemHref);
QJsonObject messageProperties = {
{ "status", "SuccessfulRequestToReplaceContent" },
{ "content_set_url", itemHref }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
emit contentSetChanged(itemHref);
}
void QmlCommerce::alreadyOwned(const QString& marketplaceId) {
auto ledger = DependencyManager::get<Ledger>();
ledger->alreadyOwned(marketplaceId);
}
QString QmlCommerce::getInstalledApps() {
QString installedAppsFromMarketplace;
QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
QDir directory(_appsPath);
QStringList apps = directory.entryList(QStringList("*.app.json"));
foreach(QString appFileName, apps) {
installedAppsFromMarketplace += appFileName;
installedAppsFromMarketplace += ",";
QFile appFile(_appsPath + appFileName);
if (appFile.open(QIODevice::ReadOnly)) {
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
appFile.close();
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptURL = appFileJsonObject["scriptURL"].toString();
// If the script .app.json is on the user's local disk but the associated script isn't running
// for some reason, start that script again.
if (!runningScripts.contains(scriptURL)) {
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptURL.trimmed())).isNull()) {
qCDebug(commerce) << "Couldn't start script while checking installed apps.";
}
}
} else {
qCDebug(commerce) << "Couldn't open local .app.json file for reading.";
}
}
return installedAppsFromMarketplace;
}
bool QmlCommerce::installApp(const QString& itemHref) {
if (!QDir(_appsPath).exists()) {
if (!QDir().mkdir(_appsPath)) {
qCDebug(commerce) << "Couldn't make _appsPath directory.";
return false;
}
}
QUrl appHref(itemHref);
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref);
if (!request) {
qCDebug(commerce) << "Couldn't create resource request for app.";
return false;
}
connect(request, &ResourceRequest::finished, this, [=]() {
if (request->getResult() != ResourceRequest::Success) {
qCDebug(commerce) << "Failed to get .app.json file from remote.";
return false;
}
// Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface
auto requestData = request->getData();
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::WriteOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file for creation.";
return false;
}
if (appFile.write(requestData) == -1) {
qCDebug(commerce) << "Couldn't write to local .app.json file.";
return false;
}
// Close the file
appFile.close();
// Read from the returned datastream to know what .js to add to Running Scripts
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(requestData);
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) {
qCDebug(commerce) << "Couldn't load script.";
return false;
}
emit appInstalled(itemHref);
return true;
});
request->send();
return true;
}
bool QmlCommerce::uninstallApp(const QString& itemHref) {
QUrl appHref(itemHref);
// Read from the file to know what .js script to stop
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::ReadOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file for deletion.";
return false;
}
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
if (!DependencyManager::get<ScriptEngines>()->stopScript(scriptUrl.trimmed(), false)) {
qCDebug(commerce) << "Couldn't stop script.";
return false;
}
// Delete the .app.json from the filesystem
// remove() closes the file first.
if (!appFile.remove()) {
qCDebug(commerce) << "Couldn't delete local .app.json file.";
return false;
}
emit appUninstalled(itemHref);
return true;
}
bool QmlCommerce::openApp(const QString& itemHref) {
QUrl appHref(itemHref);
// Read from the file to know what .html or .qml document to open
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::ReadOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file.";
return false;
}
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString homeUrl = appFileJsonObject["homeURL"].toString();
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (homeUrl.contains(".qml", Qt::CaseInsensitive)) {
tablet->loadQMLSource(homeUrl);
} else if (homeUrl.contains(".html", Qt::CaseInsensitive)) {
tablet->gotoWebScreen(homeUrl);
} else {
qCDebug(commerce) << "Attempted to open unknown type of homeURL!";
return false;
}
DependencyManager::get<HMDScriptingInterface>()->openTablet();
return true;
}

View file

@ -42,12 +42,18 @@ signals:
void historyResult(QJsonObject result);
void accountResult(QJsonObject result);
void certificateInfoResult(QJsonObject result);
void alreadyOwnedResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
void contentSetChanged(const QString& contentSetHref);
void appInstalled(const QString& appHref);
void appUninstalled(const QString& appHref);
protected:
Q_INVOKABLE void getWalletStatus();
@ -68,9 +74,20 @@ protected:
Q_INVOKABLE void account();
Q_INVOKABLE void certificateInfo(const QString& certificateId);
Q_INVOKABLE void alreadyOwned(const QString& marketplaceId);
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& itemHref);
Q_INVOKABLE QString getInstalledApps();
Q_INVOKABLE bool installApp(const QString& appHref);
Q_INVOKABLE bool uninstallApp(const QString& appHref);
Q_INVOKABLE bool openApp(const QString& appHref);
private:
QString _appsPath;
};
#endif // hifi_QmlCommerce_h

View file

@ -17,6 +17,8 @@
#include "SceneScriptingInterface.h"
OctreePacketProcessor::OctreePacketProcessor() {
setObjectName("Octree Packet Processor");
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },

View file

@ -38,7 +38,7 @@ class AccountServicesScriptingInterface : public QObject {
Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged)
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged)
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL)
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL CONSTANT)
public:
static AccountServicesScriptingInterface* getInstance();

View file

@ -94,22 +94,6 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
return result;
}
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected) {
static const char* slot = SLOT(menuItemTriggered());
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
Q_ARG(const QString&, groupName),
Q_ARG(const QStringList&, actionList),
Q_ARG(const QString&, selected),
Q_ARG(QObject*, this),
Q_ARG(const char*, slot));
}
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {
QMetaObject::invokeMethod(Menu::getInstance(), "removeActionGroup",
Q_ARG(const QString&, groupName));
}
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->isOptionChecked(menuOption);
@ -147,19 +131,3 @@ void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isCh
void MenuScriptingInterface::triggerOption(const QString& menuOption) {
QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption));
}
void MenuScriptingInterface::closeInfoView(const QString& path) {
QMetaObject::invokeMethod(Menu::getInstance(), "closeInfoView", Q_ARG(const QString&, path));
}
bool MenuScriptingInterface::isInfoViewVisible(const QString& path) {
if (QThread::currentThread() == qApp->thread()) {
return Menu::getInstance()->isInfoViewVisible(path);
}
bool result;
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isInfoViewVisible",
Q_RETURN_ARG(bool, result), Q_ARG(const QString&, path));
return result;
}

View file

@ -163,13 +163,6 @@ public slots:
*/
bool menuItemExists(const QString& menuName, const QString& menuitem);
/**
* TODO: Not working; don't document until fixed.
*/
void addActionGroup(const QString& groupName, const QStringList& actionList,
const QString& selected = QString());
void removeActionGroup(const QString& groupName);
/**jsdoc
* Check whether a checkable menu item is checked.
* @function Menu.isOptionChecked
@ -222,12 +215,6 @@ public slots:
*/
void setMenuEnabled(const QString& menuName, bool isEnabled);
/**
* TODO: Not used or useful; will not document until used.
*/
void closeInfoView(const QString& path);
bool isInfoViewVisible(const QString& path);
signals:
/**jsdoc
* Triggered when a menu item is clicked (or triggered by {@link Menu.triggerOption}).

View file

@ -32,20 +32,6 @@ static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
static const QString LAST_BROWSE_ASSETS_LOCATION_SETTING = "LastBrowseAssetsLocation";
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result) {
if (!result.value.isValid()) {
return QScriptValue::UndefinedValue;
}
Q_ASSERT(result.value.userType() == qMetaTypeId<QVariantMap>());
return engine->toScriptValue(result.value.toMap());
}
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result) {
result.value = object.toVariant();
}
WindowScriptingInterface::WindowScriptingInterface() {
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
@ -143,14 +129,6 @@ void WindowScriptingInterface::disconnectedFromDomain() {
emit domainChanged("");
}
CustomPromptResult WindowScriptingInterface::customPrompt(const QVariant& config) {
CustomPromptResult result;
bool ok = false;
auto configMap = config.toMap();
result.value = OffscreenUi::getCustomInfo(OffscreenUi::ICON_NONE, "", configMap, &ok);
return ok ? result : CustomPromptResult();
}
QString fixupPathForMac(const QString& directory) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.

View file

@ -22,17 +22,6 @@
#include <DependencyManager.h>
class CustomPromptResult {
public:
QVariant value;
};
Q_DECLARE_METATYPE(CustomPromptResult);
QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const CustomPromptResult& result);
void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result);
/**jsdoc
* The Window API provides various facilities not covered elsewhere: window dimensions, window focus, normal or entity camera
* view, clipboard, announcements, user connections, common dialog boxes, snapshots, file import, domain changes, domain
@ -142,15 +131,6 @@ public slots:
*/
void promptAsync(const QString& message = "", const QString& defaultText = "");
/**jsdoc
* Prompt the user for input in a custom, modal dialog.
* @deprecated This function is deprecated and will soon be removed.
* @function Window.customPrompt
* @param {object} config - Configures the modal dialog.
* @returns {object} The user's response.
*/
CustomPromptResult customPrompt(const QVariant& config);
/**jsdoc
* Prompt the user to choose a directory. Displays a modal dialog that navigates the directory tree.
* @function Window.browseDir
@ -325,6 +305,7 @@ public slots:
* {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured,
* {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted}
* are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings >
* NOTE: to provide a non-default value - all previous parameters must be provided.
* General > Snapshots.
* @function Window.takeSnapshot
* @param {boolean} notify=true - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken}
@ -334,8 +315,10 @@ public slots:
* @param {number} aspectRatio=0 - The width/height ratio of the snapshot required. If the value is <code>0</code> the
* full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the
* dimensions is adjusted in order to match the aspect ratio.
* @param {string} filename=QString() - If this value is not null then the image will be saved to this filename, with an appended ",jpg".
* otherwise, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'
* @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
*
* @example <caption>Using the snapshot function and signals.</caption>
* function onStillSnapshotTaken(path, notify) {
* print("Still snapshot taken: " + path);
@ -357,15 +340,18 @@ public slots:
* var notify = true;
* var animated = true;
* var aspect = 1920 / 1080;
* var filename = QString();
* var filename = "";
* Window.takeSnapshot(notify, animated, aspect, filename);
*/
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString());
/**jsdoc
* Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API.
* NOTE: to provide a non-default value - all previous parameters must be provided.
* @function Window.takeSecondaryCameraSnapshot
* @param {string} filename=QString() - If this value is not null then the image will be saved to this filename, with an appended ".jpg"
* @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by-<user name>-YYYY-MM-DD_HH-MM-SS'.
* If this parameter is <code>""</code> then the image will be saved as ".jpg".
* Otherwise, the image will be saved to this filename, with an appended ".jpg".
*
* var filename = QString();
*/

View file

@ -42,8 +42,6 @@ private:
int _domainStatusBorder;
int _magnifierBorder;
ivec2 _previousBorderSize{ -1 };
gpu::TexturePointer _uiTexture;
gpu::TexturePointer _overlayDepthTexture;
gpu::TexturePointer _overlayColorTexture;

View file

@ -125,13 +125,6 @@ Cube3DOverlay* Cube3DOverlay::createClone() const {
void Cube3DOverlay::setProperties(const QVariantMap& properties) {
Volume3DOverlay::setProperties(properties);
auto borderSize = properties["borderSize"];
if (borderSize.isValid()) {
float value = borderSize.toFloat();
setBorderSize(value);
}
}
/**jsdoc
@ -177,14 +170,8 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {number} borderSize - Not used.
*/
QVariant Cube3DOverlay::getProperty(const QString& property) {
if (property == "borderSize") {
return _borderSize;
}
return Volume3DOverlay::getProperty(property);
}

View file

@ -29,10 +29,6 @@ public:
virtual Cube3DOverlay* createClone() const override;
float getBorderSize() const { return _borderSize; }
void setBorderSize(float value) { _borderSize = value; }
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
@ -40,7 +36,6 @@ protected:
Transform evalRenderTransform() override;
private:
float _borderSize;
// edges on a cube
std::array<int, 12> _geometryIds;
};

View file

@ -388,21 +388,6 @@ QString Overlays::getOverlayType(OverlayID overlayId) {
return "";
}
QObject* Overlays::getOverlayObject(OverlayID id) {
if (QThread::currentThread() != thread()) {
QObject* result;
PROFILE_RANGE(script, __FUNCTION__);
BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id));
return result;
}
Overlay::Pointer thisOverlay = getOverlay(id);
if (thisOverlay) {
return qobject_cast<QObject*>(&(*thisOverlay));
}
return nullptr;
}
OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
if (!_enabled) {
return UNKNOWN_OVERLAY_ID;

View file

@ -98,6 +98,11 @@ public:
OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
OverlayID addOverlay(const Overlay::Pointer& overlay);
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
bool mousePressEvent(QMouseEvent* event);
bool mouseDoublePressEvent(QMouseEvent* event);
bool mouseReleaseEvent(QMouseEvent* event);
@ -227,15 +232,6 @@ public slots:
*/
QString getOverlayType(OverlayID overlayId);
/**jsdoc
* Get the overlay script object.
* @function Overlays.getOverlayObject
* @deprecated This function is deprecated and will soon be removed.
* @param {Uuid} overlayID - The ID of the overlay to get the script object of.
* @returns {object} The script object for the overlay if found.
*/
QObject* getOverlayObject(OverlayID id);
/**jsdoc
* Get the ID of the 2D overlay at a particular point on the screen or HUD.
* @function Overlays.getOverlayAtPoint
@ -356,28 +352,6 @@ public slots:
bool visibleOnly = false,
bool collidableOnly = false);
// TODO: Apart from the name, this function signature on JavaScript is identical to that of findRayIntersection() and should
// probably be removed from the JavaScript API so as not to cause confusion.
/**jsdoc
* Find the closest 3D overlay intersected by a {@link PickRay}.
* @function Overlays.findRayIntersectionVector
* @deprecated Use {@link Overlays.findRayIntersection} instead; it has identical parameters and results.
* @param {PickRay} pickRay - The PickRay to use for finding overlays.
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
* @param {Array.<Uuid>} [overlayIDsToInclude=[]] - Whitelist for intersection test. If empty then the result isn't limited
* to overlays in the list.
* @param {Array.<Uuid>} [overlayIDsToExclude=[]] - Blacklist for intersection test. If empty then the result doesn't
* exclude overlays in the list.
* @param {boolean} [visibleOnly=false] - <em>Unused</em>; exists to match Entity API.
* @param {boolean} [collidableOnly=false] - <em>Unused</em>; exists to match Entity API.
* @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by <code>pickRay</code>, taking
* into account <code>overlayIDsToInclude</code> and <code>overlayIDsToExclude</code> if they're not empty.
*/
RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
/**jsdoc
* Return a list of 3D overlays with bounding boxes that touch a search sphere.
* @function Overlays.findOverlays

View file

@ -99,13 +99,6 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
}
}
}
auto borderSize = properties["borderSize"];
if (borderSize.isValid()) {
float value = borderSize.toFloat();
setBorderSize(value);
}
}
/**jsdoc
@ -153,13 +146,8 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
*
* @property {Shape} shape=Hexagon - The geometrical shape of the overlay.
* @property {number} borderSize - Not used.
*/
QVariant Shape3DOverlay::getProperty(const QString& property) {
if (property == "borderSize") {
return _borderSize;
}
if (property == "shape") {
return shapeStrings[_shape];
}

View file

@ -30,10 +30,6 @@ public:
virtual Shape3DOverlay* createClone() const override;
float getBorderSize() const { return _borderSize; }
void setBorderSize(float value) { _borderSize = value; }
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
@ -41,7 +37,6 @@ protected:
Transform evalRenderTransform() override;
private:
float _borderSize;
GeometryCache::Shape _shape { GeometryCache::Hexagon };
};

View file

@ -434,9 +434,13 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
bool success { false };
if (QThread::currentThread() == thread()) {
glm::vec3 originalPosition = position;
bool onOwnerThread = (QThread::currentThread() == thread());
glm::vec3 poseSetTrans;
if (onOwnerThread) {
if (isIndexValid(jointIndex)) {
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
poseSetTrans = _internalPoseSet._absolutePoses[jointIndex].trans();
position = (rotation * poseSetTrans) + translation;
success = true;
} else {
success = false;
@ -444,7 +448,8 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
} else {
QReadLocker readLock(&_externalPoseSetLock);
if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) {
position = (rotation * _externalPoseSet._absolutePoses[jointIndex].trans()) + translation;
poseSetTrans = _externalPoseSet._absolutePoses[jointIndex].trans();
position = (rotation * poseSetTrans) + translation;
success = true;
} else {
success = false;
@ -452,7 +457,14 @@ bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm:
}
if (isNaN(position)) {
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produces NaN";
qCWarning(animation) << "Rig::getJointPositionInWorldFrame produced NaN."
<< " is owner thread = " << onOwnerThread
<< " position = " << originalPosition
<< " translation = " << translation
<< " rotation = " << rotation
<< " poseSetTrans = " << poseSetTrans
<< " success = " << success
<< " jointIndex = " << jointIndex;
success = false;
position = glm::vec3(0.0f);
}

View file

@ -373,7 +373,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
// The result is unique among all avatars present at the time.
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged)
Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged)
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
Q_PROPERTY(QStringList jointNames READ getJointNames)
@ -702,6 +702,7 @@ public:
signals:
void displayNameChanged();
void sessionDisplayNameChanged();
void skeletonModelURLChanged();
void lookAtSnappingChanged(bool enabled);
void sessionUUIDChanged();

View file

@ -16,6 +16,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) :
{
QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged);
QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged);
QObject::connect(avatarData.get(), &AvatarData::skeletonModelURLChanged, this, &ScriptAvatarData::skeletonModelURLChanged);
QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged);
}

View file

@ -51,7 +51,7 @@ class ScriptAvatarData : public QObject {
//
// ATTACHMENT AND JOINT PROPERTIES
//
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged)
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData)
Q_PROPERTY(QStringList jointNames READ getJointNames)
@ -132,6 +132,7 @@ public:
signals:
void displayNameChanged();
void sessionDisplayNameChanged();
void skeletonModelURLChanged();
void lookAtSnappingChanged(bool enabled);
public slots:

View file

@ -41,8 +41,8 @@ QList<EntityItemID> EntityEditFilters::getZonesByPosition(glm::vec3& position) {
return zones;
}
bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged,
EntityTree::FilterType filterType, EntityItemID& itemID) {
bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut,
bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, EntityItemPointer& existingEntity) {
// get the ids of all the zones (plus the global entity edit filter) that the position
// lies within
@ -61,6 +61,17 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper
if (filterData.rejectAll) {
return false;
}
// check to see if this filter wants to filter this message type
if ((!filterData.wantsToFilterEdit && filterType == EntityTree::FilterType::Edit) ||
(!filterData.wantsToFilterPhysics && filterType == EntityTree::FilterType::Physics) ||
(!filterData.wantsToFilterDelete && filterType == EntityTree::FilterType::Delete) ||
(!filterData.wantsToFilterAdd && filterType == EntityTree::FilterType::Add)) {
wasChanged = false;
return true; // accept the message
}
auto oldProperties = propertiesIn.getDesiredProperties();
auto specifiedProperties = propertiesIn.getChangedProperties();
propertiesIn.setDesiredProperties(specifiedProperties);
@ -68,16 +79,62 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper
propertiesIn.setDesiredProperties(oldProperties);
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
QScriptValueList args;
args << inputValues;
args << filterType;
// get the current properties for then entity and include them for the filter call
if (existingEntity && filterData.wantsOriginalProperties) {
auto currentProperties = existingEntity->getProperties(filterData.includedOriginalProperties);
QScriptValue currentValues = currentProperties.copyToScriptValue(filterData.engine, false, true, true);
args << currentValues;
}
// get the zone properties
if (filterData.wantsZoneProperties) {
auto zoneEntity = _tree->findEntityByEntityItemID(id);
if (zoneEntity) {
auto zoneProperties = zoneEntity->getProperties(filterData.includedZoneProperties);
QScriptValue zoneValues = zoneProperties.copyToScriptValue(filterData.engine, false, true, true);
if (filterData.wantsZoneBoundingBox) {
bool success = true;
AABox aaBox = zoneEntity->getAABox(success);
if (success) {
QScriptValue boundingBox = filterData.engine->newObject();
QScriptValue bottomRightNear = vec3toScriptValue(filterData.engine, aaBox.getCorner());
QScriptValue topFarLeft = vec3toScriptValue(filterData.engine, aaBox.calcTopFarLeft());
QScriptValue center = vec3toScriptValue(filterData.engine, aaBox.calcCenter());
QScriptValue boundingBoxDimensions = vec3toScriptValue(filterData.engine, aaBox.getDimensions());
boundingBox.setProperty("brn", bottomRightNear);
boundingBox.setProperty("tfl", topFarLeft);
boundingBox.setProperty("center", center);
boundingBox.setProperty("dimensions", boundingBoxDimensions);
zoneValues.setProperty("boundingBox", boundingBox);
}
}
// If this is an add or delete, or original properties weren't requested
// there won't be original properties in the args, but zone properties need
// to be the fourth parameter, so we need to pad the args accordingly
int EXPECTED_ARGS = 3;
if (args.length() < EXPECTED_ARGS) {
args << QScriptValue();
}
assert(args.length() == EXPECTED_ARGS); // we MUST have 3 args by now!
args << zoneValues;
}
}
QScriptValue result = filterData.filterFn.call(_nullObjectForFilter, args);
if (filterData.uncaughtExceptions()) {
return false;
}
if (result.isObject()){
if (result.isObject()) {
// make propertiesIn reflect the changes, for next filter...
propertiesIn.copyFromScriptValue(result, false);
@ -86,6 +143,17 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
auto out = QJsonValue::fromVariant(result.toVariant());
wasChanged |= (in != out);
} else if (result.isBool()) {
// if the filter returned false, then it's authoritative
if (!result.toBool()) {
return false;
}
// otherwise, assume it wants to pass all properties
propertiesOut = propertiesIn;
wasChanged = false;
} else {
return false;
}
@ -182,8 +250,8 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
qDebug() << "script request completed for entity " << entityID;
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
const QString urlString = scriptRequest->getUrl().toString();
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
const QString urlString = scriptRequest->getUrl().toString();
auto scriptContents = scriptRequest->getData();
qInfo() << "Downloaded script:" << scriptContents;
QScriptProgram program(scriptContents, urlString);
@ -207,6 +275,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
entitiesObject.setProperty("ADD_FILTER_TYPE", EntityTree::FilterType::Add);
entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit);
entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics);
entitiesObject.setProperty("DELETE_FILTER_TYPE", EntityTree::FilterType::Delete);
global.setProperty("Entities", entitiesObject);
filterData.filterFn = global.property("filter");
if (!filterData.filterFn.isFunction()) {
@ -214,8 +283,86 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
delete engine;
filterData.rejectAll=true;
}
// if the wantsToFilterEdit is a boolean evaluate as a boolean, otherwise assume true
QScriptValue wantsToFilterAddValue = filterData.filterFn.property("wantsToFilterAdd");
filterData.wantsToFilterAdd = wantsToFilterAddValue.isBool() ? wantsToFilterAddValue.toBool() : true;
// if the wantsToFilterEdit is a boolean evaluate as a boolean, otherwise assume true
QScriptValue wantsToFilterEditValue = filterData.filterFn.property("wantsToFilterEdit");
filterData.wantsToFilterEdit = wantsToFilterEditValue.isBool() ? wantsToFilterEditValue.toBool() : true;
// if the wantsToFilterPhysics is a boolean evaluate as a boolean, otherwise assume true
QScriptValue wantsToFilterPhysicsValue = filterData.filterFn.property("wantsToFilterPhysics");
filterData.wantsToFilterPhysics = wantsToFilterPhysicsValue.isBool() ? wantsToFilterPhysicsValue.toBool() : true;
// if the wantsToFilterDelete is a boolean evaluate as a boolean, otherwise assume false
QScriptValue wantsToFilterDeleteValue = filterData.filterFn.property("wantsToFilterDelete");
filterData.wantsToFilterDelete = wantsToFilterDeleteValue.isBool() ? wantsToFilterDeleteValue.toBool() : false;
// check to see if the filterFn has properties asking for Original props
QScriptValue wantsOriginalPropertiesValue = filterData.filterFn.property("wantsOriginalProperties");
// if the wantsOriginalProperties is a boolean, or a string, or list of strings, then evaluate as follows:
// - boolean - true - include all original properties
// false - no properties at all
// - string - empty - no properties at all
// any valid property - include just that property in the Original properties
// - list of strings - include only those properties in the Original properties
if (wantsOriginalPropertiesValue.isBool()) {
filterData.wantsOriginalProperties = wantsOriginalPropertiesValue.toBool();
} else if (wantsOriginalPropertiesValue.isString()) {
auto stringValue = wantsOriginalPropertiesValue.toString();
filterData.wantsOriginalProperties = !stringValue.isEmpty();
if (filterData.wantsOriginalProperties) {
EntityPropertyFlagsFromScriptValue(wantsOriginalPropertiesValue, filterData.includedOriginalProperties);
}
} else if (wantsOriginalPropertiesValue.isArray()) {
EntityPropertyFlagsFromScriptValue(wantsOriginalPropertiesValue, filterData.includedOriginalProperties);
filterData.wantsOriginalProperties = !filterData.includedOriginalProperties.isEmpty();
}
// check to see if the filterFn has properties asking for Zone props
QScriptValue wantsZonePropertiesValue = filterData.filterFn.property("wantsZoneProperties");
// if the wantsZoneProperties is a boolean, or a string, or list of strings, then evaluate as follows:
// - boolean - true - include all Zone properties
// false - no properties at all
// - string - empty - no properties at all
// any valid property - include just that property in the Zone properties
// - list of strings - include only those properties in the Zone properties
if (wantsZonePropertiesValue.isBool()) {
filterData.wantsZoneProperties = wantsZonePropertiesValue.toBool();
filterData.wantsZoneBoundingBox = filterData.wantsZoneProperties; // include this too
} else if (wantsZonePropertiesValue.isString()) {
auto stringValue = wantsZonePropertiesValue.toString();
filterData.wantsZoneProperties = !stringValue.isEmpty();
if (filterData.wantsZoneProperties) {
if (stringValue == "boundingBox") {
filterData.wantsZoneBoundingBox = true;
} else {
EntityPropertyFlagsFromScriptValue(wantsZonePropertiesValue, filterData.includedZoneProperties);
}
}
} else if (wantsZonePropertiesValue.isArray()) {
auto length = wantsZonePropertiesValue.property("length").toInteger();
for (int i = 0; i < length; i++) {
auto stringValue = wantsZonePropertiesValue.property(i).toString();
if (!stringValue.isEmpty()) {
filterData.wantsZoneProperties = true;
// boundingBox is a special case since it's not a true EntityPropertyFlag, so we
// need to detect it here.
if (stringValue == "boundingBox") {
filterData.wantsZoneBoundingBox = true;
break; // we can break here, since there are no other special cases
}
}
}
if (filterData.wantsZoneProperties) {
EntityPropertyFlagsFromScriptValue(wantsZonePropertiesValue, filterData.includedZoneProperties);
}
}
_lock.lockForWrite();
_filterDataMap.insert(entityID, filterData);
_lock.unlock();
@ -227,6 +374,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
}
}
} else if (scriptRequest) {
const QString urlString = scriptRequest->getUrl().toString();
qCritical() << "Failed to download script at" << urlString;
// See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure.
qCritical() << "ResourceRequest error was" << scriptRequest->getResult();

View file

@ -28,6 +28,18 @@ class EntityEditFilters : public QObject, public Dependency {
public:
struct FilterData {
QScriptValue filterFn;
bool wantsOriginalProperties { false };
bool wantsZoneProperties { false };
bool wantsToFilterAdd { true };
bool wantsToFilterEdit { true };
bool wantsToFilterPhysics { true };
bool wantsToFilterDelete { true };
EntityPropertyFlags includedOriginalProperties;
EntityPropertyFlags includedZoneProperties;
bool wantsZoneBoundingBox { false };
std::function<bool()> uncaughtExceptions;
QScriptEngine* engine;
bool rejectAll;
@ -43,7 +55,7 @@ public:
void removeFilter(EntityItemID entityID);
bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged,
EntityTree::FilterType filterType, EntityItemID& entityID);
EntityTree::FilterType filterType, EntityItemID& entityID, EntityItemPointer& existingEntity);
signals:
void filterAdded(EntityItemID id, bool success);

View file

@ -101,6 +101,11 @@ bool EntityScriptingInterface::canWriteAssets() {
return nodeList->getThisNodeCanWriteAssets();
}
bool EntityScriptingInterface::canReplaceContent() {
auto nodeList = DependencyManager::get<NodeList>();
return nodeList->getThisNodeCanReplaceContent();
}
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
if (_entityTree) {
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
@ -588,7 +593,10 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
if (entity->getLocked()) {
shouldDelete = false;
} else {
_entityTree->deleteEntity(entityID);
// only delete local entities, server entities will round trip through the server filters
if (entity->getClientOnly()) {
_entityTree->deleteEntity(entityID);
}
}
}
});

View file

@ -144,6 +144,12 @@ public slots:
*/
Q_INVOKABLE bool canWriteAssets();
/**jsdoc
* @function Entities.canReplaceContent
* @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set
*/
Q_INVOKABLE bool canReplaceContent();
/**jsdoc
* 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.

View file

@ -1111,7 +1111,7 @@ bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemP
if (entityEditFilters) {
auto position = existingEntity ? existingEntity->getWorldPosition() : propertiesIn.getPosition();
auto entityID = existingEntity ? existingEntity->getEntityItemID() : EntityItemID();
accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID);
accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID, existingEntity);
}
return accepted;
@ -1874,6 +1874,36 @@ void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) {
}
bool EntityTree::shouldEraseEntity(EntityItemID entityID, const SharedNodePointer& sourceNode) {
EntityItemPointer existingEntity;
auto startLookup = usecTimestampNow();
existingEntity = findEntityByEntityItemID(entityID);
auto endLookup = usecTimestampNow();
_totalLookupTime += endLookup - startLookup;
auto startFilter = usecTimestampNow();
FilterType filterType = FilterType::Delete;
EntityItemProperties dummyProperties;
bool wasChanged = false;
bool allowed = (sourceNode->isAllowedEditor()) || filterProperties(existingEntity, dummyProperties, dummyProperties, wasChanged, filterType);
auto endFilter = usecTimestampNow();
_totalFilterTime += endFilter - startFilter;
if (allowed) {
if (wantEditLogging() || wantTerseEditLogging()) {
qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityID;
}
} else if (wantEditLogging() || wantTerseEditLogging()) {
qCDebug(entities) << "User [" << sourceNode->getUUID() << "] attempted to deleteentity. ID:" << entityID << " Filter rejected erase.";
}
return allowed;
}
// TODO: consider consolidating processEraseMessageDetails() and processEraseMessage()
int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
#ifdef EXTRA_ERASE_DEBUGGING
@ -1901,12 +1931,10 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo
#endif
EntityItemID entityItemID(entityID);
entityItemIDsToDelete << entityItemID;
if (wantEditLogging() || wantTerseEditLogging()) {
qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID;
if (shouldEraseEntity(entityID, sourceNode)) {
entityItemIDsToDelete << entityItemID;
}
}
deleteEntities(entityItemIDsToDelete, true, true);
}
@ -1952,10 +1980,9 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons
#endif
EntityItemID entityItemID(entityID);
entityItemIDsToDelete << entityItemID;
if (wantEditLogging() || wantTerseEditLogging()) {
qCDebug(entities) << "User [" << sourceNode->getUUID() << "] deleting entity. ID:" << entityItemID;
if (shouldEraseEntity(entityID, sourceNode)) {
entityItemIDsToDelete << entityItemID;
}
}

View file

@ -57,7 +57,8 @@ public:
enum FilterType {
Add,
Edit,
Physics
Physics,
Delete
};
EntityTree(bool shouldReaverage = false);
virtual ~EntityTree();
@ -193,6 +194,8 @@ public:
int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode);
int processEraseMessageDetails(const QByteArray& buffer, const SharedNodePointer& sourceNode);
bool shouldEraseEntity(EntityItemID entityID, const SharedNodePointer& sourceNode);
EntityTreeElementPointer getContainingElement(const EntityItemID& entityItemID) /*const*/;
void addEntityMapEntry(EntityItemPointer entity);

View file

@ -97,6 +97,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const
auto mutableProperties = properties;
mutableProperties.markAllChanged();
newEntityItem = factory(entityID, mutableProperties);
newEntityItem->moveToThread(qApp->thread());
}
return newEntityItem;
}

View file

@ -331,9 +331,9 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
qmlComponent->deleteLater();
onItemCreated(qmlContext, newItem);
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
if (!rootCreated) {
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
onRootCreated();
emit rootItemCreated(newItem);
// Call this callback after rootitem is set, otherwise VrMenu wont work

View file

@ -389,19 +389,6 @@ QString OffscreenUi::getItem(const Icon icon, const QString& title, const QStrin
return result.toString();
}
QVariant OffscreenUi::getCustomInfo(const Icon icon, const QString& title, const QVariantMap& config, bool* ok) {
if (ok) {
*ok = false;
}
QVariant result = DependencyManager::get<OffscreenUi>()->customInputDialog(icon, title, config);
if (ok && result.isValid()) {
*ok = true;
}
return result;
}
ModalDialogListener* OffscreenUi::getTextAsync(const Icon icon, const QString& title, const QString& label, const QString& text) {
return DependencyManager::get<OffscreenUi>()->inputDialogAsync(icon, title, label, text);
}
@ -423,10 +410,6 @@ ModalDialogListener* OffscreenUi::getItemAsync(const Icon icon, const QString& t
return inputDialogListener;
}
ModalDialogListener* OffscreenUi::getCustomInfoAsync(const Icon icon, const QString& title, const QVariantMap& config) {
return DependencyManager::get<OffscreenUi>()->customInputDialogAsync(icon, title, config);
}
QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current) {
if (QThread::currentThread() != thread()) {
QVariant result;

View file

@ -230,10 +230,8 @@ public:
static QString getText(const Icon icon, const QString & title, const QString & label, const QString & text = QString(), bool * ok = 0);
static QString getItem(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true, bool * ok = 0);
static QVariant getCustomInfo(const Icon icon, const QString& title, const QVariantMap& config, bool* ok = 0);
static ModalDialogListener* getTextAsync(const Icon icon, const QString & title, const QString & label, const QString & text = QString());
static ModalDialogListener* getItemAsync(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true);
static ModalDialogListener* getCustomInfoAsync(const Icon icon, const QString& title, const QVariantMap& config);
unsigned int getMenuUserDataId() const;
QList<QObject *> &getModalDialogListeners();

View file

@ -268,16 +268,6 @@ bool Menu::isOptionChecked(const QString& menuOption) const {
return false;
}
void Menu::closeInfoView(const QString& path) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->hide(path);
}
bool Menu::isInfoViewVisible(const QString& path) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->isVisible(path);
}
void Menu::triggerOption(const QString& menuOption) {
QAction* action = _actionHash.value(menuOption);
if (action) {
@ -538,24 +528,6 @@ void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
QMenuBar::repaint();
}
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected, QObject* receiver, const char* slot) {
auto menu = addMenu(groupName);
QActionGroup* actionGroup = new QActionGroup(menu);
actionGroup->setExclusive(true);
for (auto action : actionList) {
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected, receiver, slot);
actionGroup->addAction(item);
}
QMenuBar::repaint();
}
void Menu::removeActionGroup(const QString& groupName) {
removeMenu(groupName);
}
MenuWrapper::MenuWrapper(ui::Menu& rootMenu, QMenu* menu) : _rootMenu(rootMenu), _realMenu(menu) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->addMenuInitializer([=](VrMenu* vrMenu) {

View file

@ -110,9 +110,6 @@ public slots:
void removeSeparator(const QString& menuName, const QString& separatorName);
void removeMenuItem(const QString& menuName, const QString& menuitem);
bool menuItemExists(const QString& menuName, const QString& menuitem);
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString(),
QObject* receiver = nullptr, const char* slot = nullptr);
void removeActionGroup(const QString& groupName);
bool isOptionChecked(const QString& menuOption) const;
void setIsOptionChecked(const QString& menuOption, bool isChecked);
@ -125,9 +122,6 @@ public slots:
void toggleDeveloperMenus();
void toggleAdvancedMenus();
bool isInfoViewVisible(const QString& path);
void closeInfoView(const QString& path);
void triggerOption(const QString& menuOption);
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }

View file

@ -305,7 +305,6 @@ void OffscreenQmlSurface::onItemCreated(QQmlContext* qmlContext, QQuickItem* new
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant)));
}
void OffscreenQmlSurface::onRootCreated() {

View file

@ -40,7 +40,6 @@ function updateOverlay(entityID, queryAACube) {
blue: 255
},
alpha: 1,
// borderSize: ...,
solid: false
});
}

View file

@ -42,8 +42,9 @@ function onClicked() {
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
} else {
var entity = HMD.tabletID;
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
if (HMD.tabletID) {
Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
}
tablet.loadQMLSource(AUDIO_QML_SOURCE);
}
}

View file

@ -146,7 +146,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
this.setIgnorePointerItems = function() {
if (HMD.tabletID !== this.tabletID) {
if (HMD.tabletID && HMD.tabletID !== this.tabletID) {
this.tabletID = HMD.tabletID;
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist);
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist);
@ -479,7 +479,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
}
function mouseReleaseOnOverlay(overlayID, event) {
if (overlayID === HMD.homeButtonID && event.button === "Primary") {
if (HMD.homeButtonID && overlayID === HMD.homeButtonID && event.button === "Primary") {
Messages.sendLocalMessage("home", overlayID);
}
}

View file

@ -32,7 +32,7 @@ Script.include("/~/system/libraries/utils.js");
this.nearTablet = function(overlays) {
for (var i = 0; i < overlays.length; i++) {
if (overlays[i] === HMD.tabletID) {
if (HMD.tabletID && overlays[i] === HMD.tabletID) {
return true;
}
}
@ -44,7 +44,8 @@ Script.include("/~/system/libraries/utils.js");
};
this.pointingAtTablet = function(objectID) {
return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID;
return (HMD.tabletScreenID && objectID === HMD.tabletScreenID)
|| (HMD.homeButtonID && objectID === HMD.homeButtonID);
};
this.sendPickData = function(controllerData) {
@ -106,7 +107,7 @@ Script.include("/~/system/libraries/utils.js");
if (nearOverlay) {
var nearOverlayReady = nearOverlay.isReady(controllerData);
if (nearOverlayReady.active && nearOverlay.grabbedThingID === HMD.tabletID) {
if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) {
return this.exitModule();
}
}

View file

@ -31,7 +31,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
);
this.pointingAtTablet = function (objectID) {
return objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID;
return (HMD.tabletScreenID && objectID === HMD.tabletScreenID)
|| (HMD.homeButtonID && objectID === HMD.homeButtonID);
};
this.isReady = function (controllerData) {
@ -76,7 +77,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
: "LeftNearParentingGrabOverlay");
if (nearOverlay) {
var nearOverlayReady = nearOverlay.isReady(controllerData);
if (nearOverlayReady.active && nearOverlay.grabbedThingID === HMD.tabletID) {
if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) {
return makeRunningValues(false, [], []);
}
}

View file

@ -117,7 +117,7 @@ Script.include("/~/system/libraries/utils.js");
Overlays.editOverlay(this.grabbedThingID, reparentProps);
// resizeTablet to counter adjust offsets to account for change of scale from sensorToWorldMatrix
if (this.grabbedThingID === HMD.tabletID) {
if (HMD.tabletID && this.grabbedThingID === HMD.tabletID) {
resizeTablet(getTabletWidthFromSettings(), reparentProps.parentJointIndex);
}
@ -143,7 +143,7 @@ Script.include("/~/system/libraries/utils.js");
});
// resizeTablet to counter adjust offsets to account for change of scale from sensorToWorldMatrix
if (this.grabbedThingID === HMD.tabletID) {
if (HMD.tabletID && this.grabbedThingID === HMD.tabletID) {
resizeTablet(getTabletWidthFromSettings(), this.previousParentJointIndex[this.grabbedThingID]);
}
}

View file

@ -20,7 +20,7 @@ Script.include("/~/system/libraries/controllers.js");
var stylusTargetIDs = [];
for (var index = 0; index < stylusTargets.length; index++) {
var stylusTarget = stylusTargets[index];
if (stylusTarget.distance <= maxNormalDistance && stylusTarget.id !== HMD.tabletID) {
if (stylusTarget.distance <= maxNormalDistance && !(HMD.tabletID && stylusTarget.id === HMD.tabletID)) {
stylusTargetIDs.push(stylusTarget.id);
}
}
@ -96,7 +96,7 @@ Script.include("/~/system/libraries/controllers.js");
var i, stylusTarget;
for (i = 0; i < candidateOverlays.length; i++) {
if (candidateOverlays[i] !== HMD.tabletID &&
if (!(HMD.tabletID && candidateOverlays[i] === HMD.tabletID) &&
Overlays.getProperty(candidateOverlays[i], "visible")) {
stylusTarget = getOverlayDistance(controllerPosition, candidateOverlays[i]);
if (stylusTarget) {

View file

@ -263,7 +263,10 @@ function Grabber() {
filter: Picks.PICK_OVERLAYS,
enabled: true
});
RayPick.setIncludeItems(this.mouseRayOverlays, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
var tabletItems = getMainTabletIDs();
if (tabletItems.length > 0) {
RayPick.setIncludeItems(this.mouseRayOverlays, tabletItems);
}
var renderStates = [{name: "grabbed", end: beacon}];
this.mouseRayEntities = Pointers.createPointer(PickType.Ray, {
joint: "Mouse",

View file

@ -31,6 +31,7 @@ Script.include([
"libraries/entityCameraTool.js",
"libraries/gridTool.js",
"libraries/entityList.js",
"libraries/utils.js",
"particle_explorer/particleExplorerTool.js",
"libraries/entityIconOverlayManager.js"
]);
@ -775,8 +776,7 @@ function findClickedEntity(event) {
}
var pickRay = Camera.computePickRay(event.x, event.y);
var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
var overlayResult = Overlays.findRayIntersection(pickRay, true, getMainTabletIDs());
if (overlayResult.intersects) {
return null;
}
@ -964,7 +964,7 @@ function mouseReleaseEvent(event) {
function wasTabletClicked(event) {
var rayPick = Camera.computePickRay(event.x, event.y);
var result = Overlays.findRayIntersection(rayPick, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs());
return result.intersects;
}
@ -987,7 +987,7 @@ function mouseClickEvent(event) {
toolBar.setActive(true);
var pickRay = result.pickRay;
var foundEntity = result.entityID;
if (foundEntity === HMD.tabletID) {
if (HMD.tabletID && foundEntity === HMD.tabletID) {
return;
}
properties = Entities.getEntityProperties(foundEntity);

View file

@ -15,6 +15,7 @@
(function() { // BEGIN LOCAL_SCOPE
var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html";
var buttonName = "HELP";
var onHelpScreen = false;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
@ -30,9 +31,8 @@
if (onHelpScreen) {
tablet.gotoHomeScreen();
} else {
var tabletEntity = HMD.tabletID;
if (tabletEntity) {
Entities.editEntity(tabletEntity, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})});
if (HMD.tabletID) {
Entities.editEntity(HMD.tabletID, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})});
}
Menu.triggerOption('Help...');
onHelpScreen = true;
@ -40,29 +40,19 @@
}
function onScreenChanged(type, url) {
onHelpScreen = type === "Web" && url.startsWith("../../../html/tabletHelp.html");
onHelpScreen = type === "Web" && url.startsWith(HELP_URL);
button.editProperties({ isActive: onHelpScreen });
}
button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
var POLL_RATE = 500;
var interval = Script.setInterval(function () {
var visible = Menu.isInfoViewVisible('InfoView_html/help.html');
if (visible !== enabled) {
enabled = visible;
button.editProperties({isActive: enabled});
}
}, POLL_RATE);
Script.scriptEnding.connect(function () {
if (onHelpScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.screenChanged.disconnect(onScreenChanged);
Script.clearInterval(interval);
if (tablet) {
tablet.removeButton(button);
}

View file

@ -279,7 +279,7 @@ colpick Color Picker / colpick.com
}
/*full layout with no submit button*/
.colpick_full_ns .colpick_submit, .colpick_full_ns .colpick_current_color{
.colpick_full_ns .colpick_submit {
display:none;
}
.colpick_full_ns .colpick_new_color {
@ -320,11 +320,11 @@ colpick Color Picker / colpick.com
}
/*rgbhex layout, no submit button*/
.colpick_rgbhex_ns .colpick_submit, .colpick_rgbhex_ns .colpick_current_color{
.colpick_rgbhex_ns .colpick_submit {
display:none;
}
.colpick_rgbhex_ns .colpick_new_color{
width:68px;
width:34px;
border: 1px solid #8f8f8f;
}
.colpick_rgbhex_ns .colpick_rgb_r {
@ -379,7 +379,7 @@ colpick Color Picker / colpick.com
}
/*hex layout, no submit button*/
.colpick_hex_ns .colpick_submit, .colpick_hex_ns .colpick_current_color {
.colpick_hex_ns .colpick_submit {
display:none;
}
.colpick_hex_ns .colpick_hex_field {

View file

@ -1,14 +1,41 @@
/*
colpick Color Picker
Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed under the MIT and GPL licenses
Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed
under the MIT and GPL licenses
For usage and examples: colpick.com/plugin
*/
/* global console, document, Element, EventBridge, jQuery, navigator, window, _ $ */
(function ($) {
var colpick = function () {
var
tpl = '<div class="colpick"><div class="colpick_color"><div class="colpick_color_overlay1"><div class="colpick_color_overlay2"><div class="colpick_selector_outer"><div class="colpick_selector_inner"></div></div></div></div></div><div class="colpick_hue"><div class="colpick_hue_arrs"><div class="colpick_hue_larr"></div><div class="colpick_hue_rarr"></div></div></div><div class="colpick_new_color"></div><div class="colpick_current_color"></div><div class="colpick_hex_field"><div class="colpick_field_letter">#</div><input type="text" maxlength="6" size="6" /></div><div class="colpick_rgb_r colpick_field"><div class="colpick_field_letter">R</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_g colpick_field"><div class="colpick_field_letter">G</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_b colpick_field"><div class="colpick_field_letter">B</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_h colpick_field"><div class="colpick_field_letter">H</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_s colpick_field"><div class="colpick_field_letter">S</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_b colpick_field"><div class="colpick_field_letter">B</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_submit"></div></div>',
tpl = '<div class="colpick"><div class="colpick_color"><div class="colpick_color_overlay1">' +
'<div class="colpick_color_overlay2"><div class="colpick_selector_outer"><div class="colpick_selector_inner">' +
'</div></div></div></div></div><div class="colpick_hue"><div class="colpick_hue_arrs">' +
'<div class="colpick_hue_larr"></div><div class="colpick_hue_rarr"></div></div></div>' +
'<div class="colpick_new_color"></div><div class="colpick_current_color"></div>' +
'<div class="colpick_hex_field"><div class="colpick_field_letter">#</div>' +
'<input type="text" maxlength="6" size="6" /></div><div class="colpick_rgb_r colpick_field">' +
'<div class="colpick_field_letter">R</div><input type="text" maxlength="3" size="3" />' +
'<div class="colpick_field_arrs"><div class="colpick_field_uarr"></div>' +
'<div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_g colpick_field">' +
'<div class="colpick_field_letter">G</div><input type="text" maxlength="3" size="3" />' +
'<div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div>' +
'</div></div><div class="colpick_rgb_b colpick_field"><div class="colpick_field_letter">B</div>' +
'<input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs">' +
'<div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div>' +
'<div class="colpick_hsb_h colpick_field"><div class="colpick_field_letter">H</div>' +
'<input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs">' +
'<div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div>' +
'<div class="colpick_hsb_s colpick_field"><div class="colpick_field_letter">S</div>' +
'<input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs">' +
'<div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div>' +
'<div class="colpick_hsb_b colpick_field"><div class="colpick_field_letter">B</div>' +
'<input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs">' +
'<div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div>' +
'<div class="colpick_submit"></div></div>',
defaults = {
showEvent: 'click',
onShow: function () {},
@ -25,15 +52,15 @@ For usage and examples: colpick.com/plugin
submitText: 'OK',
height: 156
},
//Fill the inputs of the plugin
fillRGBFields = function (hsb, cal) {
// Fill the inputs of the plugin
fillRGBFields = function (hsb, cal) {
var rgb = hsbToRgb(hsb);
$(cal).data('colpick').fields
.eq(1).val(rgb.r).end()
.eq(2).val(rgb.g).end()
.eq(3).val(rgb.b).end();
},
fillHSBFields = function (hsb, cal) {
fillHSBFields = function (hsb, cal) {
$(cal).data('colpick').fields
.eq(4).val(Math.round(hsb.h)).end()
.eq(5).val(Math.round(hsb.s)).end()
@ -42,7 +69,7 @@ For usage and examples: colpick.com/plugin
fillHexFields = function (hsb, cal) {
$(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb));
},
//Set the round selector position
// Set the round selector position
setSelector = function (hsb, cal) {
$(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100}));
$(cal).data('colpick').selectorIndic.css({
@ -50,18 +77,19 @@ For usage and examples: colpick.com/plugin
top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10)
});
},
//Set the hue selector position
// Set the hue selector position
setHue = function (hsb, cal) {
$(cal).data('colpick').hue.css('top', parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h/360, 10));
$(cal).data('colpick').hue.css('top',
parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h / 360, 10));
},
//Set current and new colors
// Set current and new colors
setCurrentColor = function (hsb, cal) {
$(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb));
},
setNewColor = function (hsb, cal) {
$(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb));
},
//Called when the new color is changed
// Called when the new color is changed
change = function (ev) {
var cal = $(this).parent().parent(), col;
if (this.parentNode.className.indexOf('_hex') > 0) {
@ -91,9 +119,10 @@ For usage and examples: colpick.com/plugin
setSelector(col, cal.get(0));
setHue(col, cal.get(0));
setNewColor(col, cal.get(0));
cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]);
cal.data('colpick').onChange.apply(cal.parent(),
[col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]);
},
//Change style on blur and on focus of inputs
// Change style on blur and on focus of inputs
blur = function (ev) {
$(this).parent().removeClass('colpick_focus');
},
@ -101,13 +130,14 @@ For usage and examples: colpick.com/plugin
$(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus');
$(this).parent().addClass('colpick_focus');
},
//Increment/decrement arrows functions
// Increment/decrement arrows functions
downIncrement = function (ev) {
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
var field = $(this).parent().find('input').focus();
var current = {
el: $(this).parent().addClass('colpick_slider'),
max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 :
(this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
y: ev.pageY,
field: field,
val: parseInt(field.val(), 10),
@ -130,7 +160,7 @@ For usage and examples: colpick.com/plugin
$(document).off('mousemove', moveIncrement);
return false;
},
//Hue slider functions
// Hue slider functions
downHue = function (ev) {
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
var current = {
@ -140,20 +170,23 @@ For usage and examples: colpick.com/plugin
$(document).on('mouseup touchend',current,upHue);
$(document).on('mousemove touchmove',current,moveHue);
var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY );
var pageY = ((ev.type === 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY );
change.apply(
current.cal.data('colpick')
.fields.eq(4).val(parseInt(360*(current.cal.data('colpick').height - (pageY - current.y))/current.cal.data('colpick').height, 10))
.fields.eq(4).val(parseInt(360 * (current.cal.data('colpick').height -
(pageY - current.y)) / current.cal.data('colpick').height, 10))
.get(0),
[current.cal.data('colpick').livePreview]
);
return false;
},
moveHue = function (ev) {
var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY );
var pageY = ((ev.type === 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY );
change.apply(
ev.data.cal.data('colpick')
.fields.eq(4).val(parseInt(360*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.y))))/ev.data.cal.data('colpick').height, 10))
.fields.eq(4).val(parseInt(360 * (ev.data.cal.data('colpick').height -
Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.y)))) /
ev.data.cal.data('colpick').height, 10))
.get(0),
[ev.data.preview]
);
@ -166,7 +199,7 @@ For usage and examples: colpick.com/plugin
$(document).off('mousemove touchmove',moveHue);
return false;
},
//Color selector functions
// Color selector functions
downSelector = function (ev) {
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
var current = {
@ -178,8 +211,8 @@ For usage and examples: colpick.com/plugin
$(document).on('mouseup touchend',current,upSelector);
$(document).on('mousemove touchmove',current,moveSelector);
var payeX,pageY;
if(ev.type == 'touchstart') {
var pageX,pageY;
if (ev.type === 'touchstart') {
pageX = ev.originalEvent.changedTouches[0].pageX,
pageY = ev.originalEvent.changedTouches[0].pageY;
} else {
@ -189,16 +222,17 @@ For usage and examples: colpick.com/plugin
change.apply(
current.cal.data('colpick').fields
.eq(6).val(parseInt(100*(current.cal.data('colpick').height - (pageY - current.pos.top))/current.cal.data('colpick').height, 10)).end()
.eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10))
.get(0),
.eq(6).val(parseInt(100 * (current.cal.data('colpick').height - (pageY - current.pos.top)) /
current.cal.data('colpick').height, 10)).end()
.eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10))
.get(0),
[current.preview]
);
return false;
},
moveSelector = function (ev) {
var payeX,pageY;
if(ev.type == 'touchmove') {
var pageX,pageY;
if (ev.type === 'touchmove') {
pageX = ev.originalEvent.changedTouches[0].pageX,
pageY = ev.originalEvent.changedTouches[0].pageY;
} else {
@ -208,9 +242,12 @@ For usage and examples: colpick.com/plugin
change.apply(
ev.data.cal.data('colpick').fields
.eq(6).val(parseInt(100*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.pos.top))))/ev.data.cal.data('colpick').height, 10)).end()
.eq(5).val(parseInt(100*(Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageX - ev.data.pos.left))))/ev.data.cal.data('colpick').height, 10))
.get(0),
.eq(6).val(parseInt(100 * (ev.data.cal.data('colpick').height -
Math.max(0, Math.min(ev.data.cal.data('colpick').height, (pageY - ev.data.pos.top)))) /
ev.data.cal.data('colpick').height, 10)).end()
.eq(5).val(parseInt(100 * (Math.max(0, Math.min(ev.data.cal.data('colpick').height,
(pageX - ev.data.pos.left)))) / ev.data.cal.data('colpick').height, 10))
.get(0),
[ev.data.preview]
);
return false;
@ -222,7 +259,7 @@ For usage and examples: colpick.com/plugin
$(document).off('mousemove touchmove',moveSelector);
return false;
},
//Submit button
// Submit button
clickSubmit = function (ev) {
var cal = $(this).parent();
var col = cal.data('colpick').color;
@ -230,7 +267,7 @@ For usage and examples: colpick.com/plugin
setCurrentColor(col, cal.get(0));
cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el);
},
//Show/hide the color picker
// Show/hide the color picker
show = function (ev) {
// Prevent the trigger of any direct parent
ev.stopPropagation();
@ -245,27 +282,29 @@ For usage and examples: colpick.com/plugin
left -= calW;
}
cal.css({left: left + 'px', top: top + 'px'});
if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) {
if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) !== false) {
cal.show();
}
//Hide when user clicks outside
// Hide when user clicks outside
$('html').mousedown({cal:cal}, hide);
cal.mousedown(function(ev){ev.stopPropagation();})
cal.mousedown(function(ev){
ev.stopPropagation();
});
},
hide = function (ev) {
if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) !== false) {
ev.data.cal.hide();
}
$('html').off('mousedown', hide);
},
getViewport = function () {
var m = document.compatMode == 'CSS1Compat';
var m = document.compatMode === 'CSS1Compat';
return {
l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth)
};
},
//Fix the values if the user enters a negative or high value
// Fix the values if the user enters a negative or high value
fixHSB = function (hsb) {
return {
h: Math.min(360, Math.max(0, hsb.h)),
@ -302,70 +341,85 @@ For usage and examples: colpick.com/plugin
setSelector(col, cal.get(0));
setHue(col, cal.get(0));
setNewColor(col, cal.get(0));
// If the user triggered this behavior, then any prior color change should be negated.
cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col),
hsbToRgb(col), cal.data('colpick').el, 0]);
};
return {
init: function (opt) {
opt = $.extend({}, defaults, opt||{});
//Set color
if (typeof opt.color == 'string') {
// Set color
if (typeof opt.color === 'string') {
opt.color = hexToHsb(opt.color);
} else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
} else if (opt.color.r !== undefined && opt.color.g !== undefined && opt.color.b !== undefined) {
opt.color = rgbToHsb(opt.color);
} else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
} else if (opt.color.h !== undefined && opt.color.s !== undefined && opt.color.b !== undefined) {
opt.color = fixHSB(opt.color);
} else {
return this;
}
//For each selected DOM element
// For each selected DOM element
return this.each(function () {
//If the element does not have an ID
// If the element does not have an ID
if (!$(this).data('colpickId')) {
var options = $.extend({}, opt);
options.origColor = opt.color;
//Generate and assign a random ID
// Generate and assign a random ID
var id = 'collorpicker_' + parseInt(Math.random() * 1000);
$(this).data('colpickId', id);
//Set the tpl's ID and get the HTML
// Set the tpl's ID and get the HTML
var cal = $(tpl).attr('id', id);
//Add class according to layout
// Add class according to layout
cal.addClass('colpick_'+options.layout+(options.submit?'':' colpick_'+options.layout+'_ns'));
//Add class if the color scheme is not default
if(options.colorScheme != 'light') {
// Add class if the color scheme is not default
if (options.colorScheme !== 'light') {
cal.addClass('colpick_'+options.colorScheme);
}
//Setup submit button
// Setup submit button
cal.find('div.colpick_submit').html(options.submitText).click(clickSubmit);
//Setup input fields
// Setup input fields
options.fields = cal.find('input').change(change).blur(blur).focus(focus);
cal.find('div.colpick_field_arrs').mousedown(downIncrement).end().find('div.colpick_current_color').click(restoreOriginal);
//Setup hue selector
cal.find('div.colpick_field_arrs').mousedown(downIncrement);
cal.find('div.colpick_current_color').click(restoreOriginal);
// Setup hue selector
options.selector = cal.find('div.colpick_color').on('mousedown touchstart',downSelector);
options.selectorIndic = options.selector.find('div.colpick_selector_outer');
//Store parts of the plugin
// Store parts of the plugin
options.el = this;
options.hue = cal.find('div.colpick_hue_arrs');
huebar = options.hue.parent();
//Paint the hue bar
var huebar = options.hue.parent();
// Paint the hue bar
var UA = navigator.userAgent.toLowerCase();
var isIE = navigator.appName === 'Microsoft Internet Explorer';
var IEver = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0;
var IEver = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[.0-9]{0,})/ )[1] ) : 0;
var ngIE = ( isIE && IEver < 10 );
var stops = ['#ff0000','#ff0080','#ff00ff','#8000ff','#0000ff','#0080ff','#00ffff','#00ff80','#00ff00','#80ff00','#ffff00','#ff8000','#ff0000'];
if(ngIE) {
var stops = ['#ff0000', '#ff0080', '#ff00ff', '#8000ff', '#0000ff', '#0080ff', '#00ffff', '#00ff80',
'#00ff00', '#80ff00', '#ffff00', '#ff8000', '#ff0000'];
if (ngIE) {
var i, div;
for(i=0; i<=11; i++) {
div = $('<div></div>').attr('style','height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+')";');
for (i=0; i<=11; i++) {
div = $('<div></div>').attr('style',
'height:8.333333%; filter:progid:' +
'DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' + stops[i] +
', endColorstr=' + stops[i + 1] +
'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=' +
stops[i] + ', endColorstr=' + stops[i + 1] + ')";');
huebar.append(div);
}
} else {
stopList = stops.join(',');
huebar.attr('style','background:-webkit-linear-gradient(top,'+stopList+'); background: -o-linear-gradient(top,'+stopList+'); background: -ms-linear-gradient(top,'+stopList+'); background:-moz-linear-gradient(top,'+stopList+'); -webkit-linear-gradient(top,'+stopList+'); background:linear-gradient(to bottom,'+stopList+'); ');
var stopList = stops.join(',');
huebar.attr('style', 'background:-webkit-linear-gradient(top,' + stopList +
'); background: -o-linear-gradient(top,' + stopList +
'); background: -ms-linear-gradient(top,' + stopList +
'); background:-moz-linear-gradient(top,' + stopList +
'); -webkit-linear-gradient(top,' + stopList +
'); background:linear-gradient(to bottom,' + stopList + '); ');
}
cal.find('div.colpick_hue').on('mousedown touchstart',downHue);
options.newColor = cal.find('div.colpick_new_color');
options.currentColor = cal.find('div.colpick_current_color');
//Store options and fill with default color
// Store options and fill with default color
cal.data('colpick', options);
fillRGBFields(options.color, cal.get(0));
fillHSBFields(options.color, cal.get(0));
@ -374,7 +428,7 @@ For usage and examples: colpick.com/plugin
setSelector(options.color, cal.get(0));
setCurrentColor(options.color, cal.get(0));
setNewColor(options.color, cal.get(0));
//Append to body if flat=false, else show in place
// Append to body if flat=false, else show in place
if (options.flat) {
cal.appendTo(this).show();
cal.css({
@ -391,7 +445,7 @@ For usage and examples: colpick.com/plugin
}
});
},
//Shows the picker
// Shows the picker
showPicker: function() {
return this.each( function () {
if ($(this).data('colpickId')) {
@ -399,7 +453,7 @@ For usage and examples: colpick.com/plugin
}
});
},
//Hides the picker
// Hides the picker
hidePicker: function() {
return this.each( function () {
if ($(this).data('colpickId')) {
@ -407,14 +461,14 @@ For usage and examples: colpick.com/plugin
}
});
},
//Sets a color as new and current (default)
// Sets a color as new and current (default)
setColor: function(col, setCurrent) {
setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent;
if (typeof col == 'string') {
if (typeof col === 'string') {
col = hexToHsb(col);
} else if (col.r != undefined && col.g != undefined && col.b != undefined) {
} else if (col.r !== undefined && col.g !== undefined && col.b !== undefined) {
col = rgbToHsb(col);
} else if (col.h != undefined && col.s != undefined && col.b != undefined) {
} else if (col.h !== undefined && col.s !== undefined && col.b !== undefined) {
col = fixHSB(col);
} else {
return this;
@ -431,8 +485,9 @@ For usage and examples: colpick.com/plugin
setSelector(col, cal.get(0));
setNewColor(col, cal.get(0));
cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]);
if(setCurrent) {
cal.data('colpick').onChange.apply(cal.parent(),
[col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]);
if (setCurrent) {
setCurrentColor(col, cal.get(0));
}
}
@ -440,13 +495,23 @@ For usage and examples: colpick.com/plugin
}
};
}();
//Color space convertions
var hexToRgb = function (hex) {
var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
// Color space convertions
var hexToRgb = function (hexString) {
if (typeof hexString !== "string") {
print("Error - ColPick.js::hexToRgb expects string object.");
return;
}
var hexNumber = parseInt(((hexString.indexOf('#') > -1) ? hexString.substring(1) : hexString), 16);
return { r: hexNumber >> 16, g: (hexNumber & 0x00FF00) >> 8, b: (hexNumber & 0x0000FF)};
};
var hexToHsb = function (hex) {
return rgbToHsb(hexToRgb(hex));
var hexToHsb = function (hexString) {
if (typeof hexString !== "string") {
print("Error - ColPick.js::hexToHsb expects string object.");
return;
}
return rgbToHsb(hexToRgb(hexString));
};
var rgbToHsb = function (rgb) {
var hsb = {h: 0, s: 0, b: 0};
@ -454,14 +519,22 @@ For usage and examples: colpick.com/plugin
var max = Math.max(rgb.r, rgb.g, rgb.b);
var delta = max - min;
hsb.b = max;
hsb.s = max != 0 ? 255 * delta / max : 0;
if (hsb.s != 0) {
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
} else hsb.h = -1;
hsb.s = max != 0 ? 255 * delta / max : 0; // eslint-disable-line eqeqeq
if (hsb.s != 0) { // eslint-disable-line eqeqeq
if (rgb.r == max) { // eslint-disable-line eqeqeq
hsb.h = (rgb.g - rgb.b) / delta;
} else if (rgb.g == max) { // eslint-disable-line eqeqeq
hsb.h = 2 + (rgb.b - rgb.r) / delta;
} else {
hsb.h = 4 + (rgb.r - rgb.g) / delta;
}
} else {
hsb.h = -1;
}
hsb.h *= 60;
if (hsb.h < 0) hsb.h += 360;
if (hsb.h < 0) {
hsb.h += 360;
}
hsb.s *= 100/255;
hsb.b *= 100/255;
return hsb;
@ -471,20 +544,30 @@ For usage and examples: colpick.com/plugin
var h = hsb.h;
var s = hsb.s*255/100;
var v = hsb.b*255/100;
if(s == 0) {
if (s == 0) { // eslint-disable-line eqeqeq
rgb.r = rgb.g = rgb.b = v;
} else {
var t1 = v;
var t2 = (255-s)*v/255;
var t3 = (t1-t2)*(h%60)/60;
if(h==360) h = 0;
if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
else {rgb.r=0; rgb.g=0; rgb.b=0}
if (h==360) { // eslint-disable-line eqeqeq
h = 0;
}
if (h<60) {
rgb.r=t1; rgb.b=t2; rgb.g=t2+t3;
} else if (h<120) {
rgb.g=t1; rgb.b=t2; rgb.r=t1-t3;
} else if (h<180) {
rgb.g=t1; rgb.r=t2; rgb.b=t2+t3;
} else if (h<240) {
rgb.b=t1; rgb.r=t2; rgb.g=t1-t3;
} else if (h<300) {
rgb.b=t1; rgb.g=t2; rgb.r=t2+t3;
} else if (h<360) {
rgb.r=t1; rgb.g=t2; rgb.b=t1-t3;
} else {
rgb.r=0; rgb.g=0; rgb.b=0;
}
}
return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
};
@ -495,7 +578,7 @@ For usage and examples: colpick.com/plugin
rgb.b.toString(16)
];
$.each(hex, function (nr, val) {
if (val.length == 1) {
if (val.length == 1) { // eslint-disable-line eqeqeq
hex[nr] = '0' + val;
}
});
@ -521,3 +604,4 @@ For usage and examples: colpick.com/plugin
}
});
})(jQuery);

View file

@ -31,7 +31,7 @@ var ICON_FOR_TYPE = {
var EDITOR_TIMEOUT_DURATION = 1500;
var KEY_P = 80; // Key code for letter p used for Parenting hotkey.
var colorPickers = [];
var colorPickers = {};
var lastEntityID = null;
var MATERIAL_PREFIX_STRING = "mat::";
@ -73,8 +73,8 @@ function enableProperties() {
function disableProperties() {
disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker");
disableChildren(document, ".colpick");
for (var i = 0; i < colorPickers.length; i++) {
colorPickers[i].colpickHide();
for (var pickKey in colorPickers) {
colorPickers[pickKey].colpickHide();
}
var elLocked = document.getElementById("property-locked");
@ -86,7 +86,6 @@ function disableProperties() {
function showElements(els, show) {
for (var i = 0; i < els.length; i++) {
els[i].style.display = (show) ? 'table' : 'none';
}
}
@ -509,15 +508,6 @@ function unbindAllInputs() {
}
}
function clearSelection() {
if (document.selection && document.selection.empty) {
document.selection.empty();
} else if (window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
}
}
function showParentMaterialNameBox(number, elNumber, elString) {
if (number) {
$('#property-parent-material-id-number-container').show();
@ -827,8 +817,10 @@ function loaded() {
if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) {
saveJSONUserData(true);
}
// the event bridge and json parsing handle our avatar id string differently.
var doSelectElement = lastEntityID === '"' + properties.id + '"';
// the event bridge and json parsing handle our avatar id string differently.
lastEntityID = '"' + properties.id + '"';
elID.value = properties.id;
@ -1193,12 +1185,10 @@ function loaded() {
}
var activeElement = document.activeElement;
if (typeof activeElement.select !== "undefined") {
if (doSelectElement && typeof activeElement.select !== "undefined") {
activeElement.select();
}
}
clearSelection();
}
});
}
@ -1381,13 +1371,19 @@ function loaded() {
elColorRed.addEventListener('change', colorChangeFunction);
elColorGreen.addEventListener('change', colorChangeFunction);
elColorBlue.addEventListener('change', colorChangeFunction);
colorPickers.push($('#property-color-control2').colpick({
colorPickers['#property-color-control2'] = $('#property-color-control2').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-color-control2').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-color-control2'].colpickSetColor({
"r": elColorRed.value,
"g": elColorGreen.value,
"b": elColorBlue.value});
},
onHide: function(colpick) {
$('#property-color-control2').attr('active', 'false');
@ -1396,7 +1392,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b);
}
}));
});
elLightSpotLight.addEventListener('change', createEmitCheckedPropertyUpdateFunction('isSpotlight'));
@ -1405,13 +1401,20 @@ function loaded() {
elLightColorRed.addEventListener('change', lightColorChangeFunction);
elLightColorGreen.addEventListener('change', lightColorChangeFunction);
elLightColorBlue.addEventListener('change', lightColorChangeFunction);
colorPickers.push($('#property-light-color').colpick({
colorPickers['#property-light-color'] = $('#property-light-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-light-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-light-color'].colpickSetColor({
"r": elLightColorRed.value,
"g": elLightColorGreen.value,
"b": elLightColorBlue.value
});
},
onHide: function(colpick) {
$('#property-light-color').attr('active', 'false');
@ -1420,7 +1423,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b);
}
}));
});
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1));
elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1));
@ -1486,13 +1489,20 @@ function loaded() {
elTextTextColorRed.addEventListener('change', textTextColorChangeFunction);
elTextTextColorGreen.addEventListener('change', textTextColorChangeFunction);
elTextTextColorBlue.addEventListener('change', textTextColorChangeFunction);
colorPickers.push($('#property-text-text-color').colpick({
colorPickers['#property-text-text-color'] = $('#property-text-text-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-text-text-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-text-text-color'].colpickSetColor({
"r": elTextTextColorRed.value,
"g": elTextTextColorGreen.value,
"b": elTextTextColorBlue.value
});
},
onHide: function(colpick) {
$('#property-text-text-color').attr('active', 'false');
@ -1502,7 +1512,7 @@ function loaded() {
$(el).attr('active', 'false');
emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b);
}
}));
});
var textBackgroundColorChangeFunction = createEmitColorPropertyUpdateFunction(
'backgroundColor', elTextBackgroundColorRed, elTextBackgroundColorGreen, elTextBackgroundColorBlue);
@ -1510,13 +1520,20 @@ function loaded() {
elTextBackgroundColorRed.addEventListener('change', textBackgroundColorChangeFunction);
elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction);
elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction);
colorPickers.push($('#property-text-background-color').colpick({
colorPickers['#property-text-background-color'] = $('#property-text-background-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-text-background-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-text-background-color'].colpickSetColor({
"r": elTextBackgroundColorRed.value,
"g": elTextBackgroundColorGreen.value,
"b": elTextBackgroundColorBlue.value
});
},
onHide: function(colpick) {
$('#property-text-background-color').attr('active', 'false');
@ -1525,7 +1542,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b);
}
}));
});
// Key light
var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode',
@ -1535,13 +1552,20 @@ function loaded() {
elZoneKeyLightModeDisabled.addEventListener('change', keyLightModeChanged);
elZoneKeyLightModeEnabled.addEventListener('change', keyLightModeChanged);
colorPickers.push($('#property-zone-key-light-color').colpick({
colorPickers['#property-zone-key-light-color'] = $('#property-zone-key-light-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-zone-key-light-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-zone-key-light-color'].colpickSetColor({
"r": elZoneKeyLightColorRed.value,
"g": elZoneKeyLightColorGreen.value,
"b": elZoneKeyLightColorBlue.value
});
},
onHide: function(colpick) {
$('#property-zone-key-light-color').attr('active', 'false');
@ -1550,7 +1574,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight');
}
}));
});
var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color',
elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue);
@ -1604,13 +1628,20 @@ function loaded() {
elZoneHazeRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeRange'));
colorPickers.push($('#property-zone-haze-color').colpick({
colorPickers['#property-zone-haze-color'] = $('#property-zone-haze-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-zone-haze-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-zone-haze-color'].colpickSetColor({
"r": elZoneHazeColorRed.value,
"g": elZoneHazeColorGreen.value,
"b": elZoneHazeColorBlue.value
});
},
onHide: function(colpick) {
$('#property-zone-haze-color').attr('active', 'false');
@ -1619,7 +1650,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze');
}
}));
});
var zoneHazeColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeColor',
elZoneHazeColorRed,
elZoneHazeColorGreen,
@ -1629,13 +1660,20 @@ function loaded() {
elZoneHazeColorGreen.addEventListener('change', zoneHazeColorChangeFunction);
elZoneHazeColorBlue.addEventListener('change', zoneHazeColorChangeFunction);
colorPickers.push($('#property-zone-haze-glare-color').colpick({
colorPickers['#property-zone-haze-glare-color'] = $('#property-zone-haze-glare-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-zone-haze-glare-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-zone-haze-glare-color'].colpickSetColor({
"r": elZoneHazeGlareColorRed.value,
"g": elZoneHazeGlareColorGreen.value,
"b": elZoneHazeGlareColorBlue.value
});
},
onHide: function(colpick) {
$('#property-zone-haze-glare-color').attr('active', 'false');
@ -1644,7 +1682,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze');
}
}));
});
var zoneHazeGlareColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeGlareColor',
elZoneHazeGlareColorRed,
elZoneHazeGlareColorGreen,
@ -1671,13 +1709,20 @@ function loaded() {
elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction);
elZoneSkyboxColorGreen.addEventListener('change', zoneSkyboxColorChangeFunction);
elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction);
colorPickers.push($('#property-zone-skybox-color').colpick({
colorPickers['#property-zone-skybox-color'] = $('#property-zone-skybox-color').colpick({
colorScheme: 'dark',
layout: 'hex',
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
$('#property-zone-skybox-color').attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
colorPickers['#property-zone-skybox-color'].colpickSetColor({
"r": elZoneSkyboxColorRed.value,
"g": elZoneSkyboxColorGreen.value,
"b": elZoneSkyboxColorBlue.value
});
},
onHide: function(colpick) {
$('#property-zone-skybox-color').attr('active', 'false');
@ -1686,7 +1731,7 @@ function loaded() {
$(el).css('background-color', '#' + hex);
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox');
}
}));
});
elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox', 'url'));
@ -1761,34 +1806,13 @@ function loaded() {
};
// For input and textarea elements, select all of the text on focus
// WebKit-based browsers, such as is used with QWebView, have a quirk
// where the mouseup event comes after the focus event, causing the
// text to be deselected immediately after selecting all of the text.
// To make this work we block the first mouseup event after the elements
// received focus. If we block all mouseup events the user will not
// be able to click within the selected text.
// We also check to see if the value has changed to make sure we aren't
// blocking a mouse-up event when clicking on an input spinner.
var els = document.querySelectorAll("input, textarea");
for (var i = 0; i < els.length; i++) {
var clicked = false;
var originalText;
// TODO FIXME: (JSHint) Functions declared within loops referencing
// an outer scoped variable may lead to confusing semantics.
els[i].onfocus = function(e) {
originalText = this.value;
this.select();
clicked = false;
};
// TODO FIXME: (JSHint) Functions declared within loops referencing
// an outer scoped variable may lead to confusing semantics.
els[i].onmouseup = function(e) {
if (!clicked && originalText === this.value) {
e.preventDefault();
}
clicked = true;
els[i].onfocus = function (e) {
e.target.select();
};
}
bindAllNonJSONEditorElements();
});

View file

@ -306,10 +306,6 @@ WebTablet.prototype.setScriptURL = function (scriptURL) {
Overlays.editOverlay(this.webOverlayID, { scriptURL: scriptURL });
};
WebTablet.prototype.getOverlayObject = function () {
return Overlays.getOverlayObject(this.webOverlayID);
};
WebTablet.prototype.setWidth = function (width) {
// imported from libraries/utils.js
resizeTablet(width);
@ -335,7 +331,7 @@ WebTablet.prototype.destroy = function () {
};
WebTablet.prototype.geometryChanged = function (geometry) {
if (!HMD.active) {
if (!HMD.active && HMD.tabletID) {
var tabletProperties = {};
// compute position, rotation & parentJointIndex of the tablet
this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);
@ -463,6 +459,9 @@ WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMou
};
WebTablet.prototype.onHmdChanged = function () {
if (!HMD.tabletID) {
return;
}
var tabletProperties = {};
// compute position, rotation & parentJointIndex of the tablet
this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);

View file

@ -21,7 +21,10 @@ SPACE_LOCAL = "local";
SPACE_WORLD = "world";
HIGHLIGHT_LIST_NAME = "editHandleHighlightList";
Script.include("./controllers.js");
Script.include([
"./controllers.js",
"./utils.js"
]);
SelectionManager = (function() {
var that = {};
@ -668,7 +671,7 @@ SelectionDisplay = (function() {
var pickRay = generalComputePickRay(event.x, event.y);
// TODO_Case6491: Move this out to setup just to make it once
var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID];
var interactiveOverlays = getMainTabletIDs();
for (var key in handleTools) {
if (handleTools.hasOwnProperty(key)) {
interactiveOverlays.push(key);
@ -681,8 +684,8 @@ SelectionDisplay = (function() {
var results = testRayIntersect(pickRay, interactiveOverlays);
if (results.intersects) {
var hitOverlayID = results.overlayID;
if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) ||
(hitOverlayID === HMD.homeButtonID)) {
if ((HMD.tabletID && hitOverlayID === HMD.tabletID) || (HMD.tabletScreenID && hitOverlayID === HMD.tabletScreenID)
|| (HMD.homeButtonID && hitOverlayID === HMD.homeButtonID)) {
// EARLY EXIT-(mouse clicks on the tablet should override the edit affordances)
return false;
}

View file

@ -428,3 +428,17 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
};
getMainTabletIDs = function () {
var tabletIDs = [];
if (HMD.tabletID) {
tabletIDs.push(HMD.tabletID);
}
if (HMD.tabletScreenID) {
tabletIDs.push(HMD.tabletScreenID);
}
if (HMD.homeButtonID) {
tabletIDs.push(HMD.homeButtonID);
}
return tabletIDs;
};

View file

@ -75,11 +75,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
tablet.sendToQml({
method: 'updateCheckoutQML', params: {
itemId: '0d90d21c-ce7a-4990-ad18-e9d2cf991027',
itemName: 'Test Flaregun',
itemPrice: (debugError ? 10 : 17),
itemHref: 'http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json',
categories: ["Wearables", "Miscellaneous"]
itemId: '424611a2-73d0-4c03-9087-26a6a279257b',
itemName: '2018-02-15 Finnegon',
itemPrice: (debugError ? 10 : 3),
itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst',
categories: ["Miscellaneous"]
}
});
}
@ -104,8 +104,9 @@ var selectionDisplay = null; // for gridTool.js to ignore
tablet.gotoHomeScreen();
} else {
Wallet.refreshWalletStatus();
var entity = HMD.tabletID;
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
if (HMD.tabletID) {
Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
}
showMarketplace();
}
}
@ -232,13 +233,19 @@ var selectionDisplay = null; // for gridTool.js to ignore
return position;
}
function rezEntity(itemHref, isWearable) {
function rezEntity(itemHref, itemType) {
var isWearable = itemType === "wearable";
var success = Clipboard.importEntities(itemHref);
var wearableLocalPosition = null;
var wearableLocalRotation = null;
var wearableLocalDimensions = null;
var wearableDimensions = null;
if (itemType === "contentSet") {
console.log("Item is a content set; codepath shouldn't go here.")
return;
}
if (isWearable) {
var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
if (!wearableTransforms) {
@ -544,7 +551,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
break;
case 'checkout_rezClicked':
case 'purchases_rezClicked':
rezEntity(message.itemHref, message.isWearable);
rezEntity(message.itemHref, message.itemType);
break;
case 'header_marketplaceImageClicked':
case 'purchases_backClicked':

View file

@ -28,8 +28,9 @@ var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
} else {
var entity = HMD.tabletID;
Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})});
if (HMD.tabletID) {
Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
}
tablet.gotoMenuScreen();
}
}

View file

@ -41,14 +41,14 @@
if (!UIWebTablet) {
return false;
}
if (Overlays.getProperty(HMD.tabletID, "type") != "model") {
if (Overlays.getProperty(HMD.tabletID, "type") !== "model") {
if (debugTablet) {
print("TABLET is invalid due to frame: " + JSON.stringify(Overlays.getProperty(HMD.tabletID, "type")));
}
return false;
}
if (Overlays.getProperty(HMD.homeButtonID, "type") != "circle3d" ||
Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") {
if (Overlays.getProperty(HMD.homeButtonID, "type") !== "circle3d" ||
Overlays.getProperty(HMD.tabletScreenID, "type") !== "web3d") {
if (debugTablet) {
print("TABLET is invalid due to other");
}
@ -112,7 +112,7 @@
}
function showTabletUI() {
checkTablet()
checkTablet();
if (!tabletRezzed || !tabletIsValid()) {
closeTabletUI();
@ -157,7 +157,7 @@
}
function closeTabletUI() {
checkTablet()
checkTablet();
gTablet.tabletShown = false;
if (UIWebTablet) {
if (UIWebTablet.onClose) {
@ -178,14 +178,14 @@
print("TABLET closeTabletUI, UIWebTablet is null");
}
tabletRezzed = false;
gTablet = null
gTablet = null;
}
function updateShowTablet() {
var now = Date.now();
checkTablet()
checkTablet();
// close the WebTablet if it we go into toolbar mode.
var tabletShown = gTablet.tabletShown;
@ -270,7 +270,7 @@
}
if (channel === "home") {
if (UIWebTablet) {
checkTablet()
checkTablet();
gTablet.landscape = false;
}
}

View file

@ -48,9 +48,8 @@
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
} else {
var tabletEntity = HMD.tabletID;
if (tabletEntity) {
Entities.editEntity(tabletEntity, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})});
if (HMD.tabletID) {
Entities.editEntity(HMD.tabletID, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})});
}
shouldActivateButton = true;
tablet.gotoWebScreen(USERS_URL);

View file

@ -0,0 +1,41 @@
//
// keep-in-zone-example.js
//
//
// Created by Brad Hefta-Gaub to use Entities on Dec. 15, 2017
// Copyright 2017 High Fidelity, Inc.
//
// This sample entity edit filter script will keep any entity inside the bounding box of the zone this filter is applied to.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function filter(properties, type, originalProperties, zoneProperties) {
var nearZero = 0.0001 * Math.random() + 0.001;
/* Clamp position changes to bounding box of zone.*/
function clamp(val, min, max) {
/* Random near-zero value used as "zero" to prevent two sequential updates from being
exactly the same (which would cause them to be ignored) */
if (val > max) {
val = max - nearZero;
} else if (val < min) {
val = min + nearZero;
}
return val;
}
if (properties.position) {
properties.position.x = clamp(properties.position.x, zoneProperties.boundingBox.brn.x, zoneProperties.boundingBox.tfl.x);
properties.position.y = clamp(properties.position.y, zoneProperties.boundingBox.brn.y, zoneProperties.boundingBox.tfl.y);
properties.position.z = clamp(properties.position.z, zoneProperties.boundingBox.brn.z, zoneProperties.boundingBox.tfl.z);
}
return properties;
}
filter.wantsOriginalProperties = true;
filter.wantsZoneProperties = true;
filter;

View file

@ -0,0 +1,45 @@
//
// position-example.js
//
//
// Created by Brad Hefta-Gaub to use Entities on Dec. 15, 2017
// Copyright 2017 High Fidelity, Inc.
//
// This sample entity edit filter script will only allow position to be changed by no more than 5 meters on any axis.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function filter(properties, type, originalProperties) {
/* Clamp position changes.*/
var maxChange = 5;
function clamp(val, min, max) {
if (val > max) {
val = max;
} else if (val < min) {
val = min;
}
return val;
}
if (properties.position) {
/* Random near-zero value used as "zero" to prevent two sequential updates from being
exactly the same (which would cause them to be ignored) */
var nearZero = 0.0001 * Math.random() + 0.001;
var maxFudgeChange = (maxChange + nearZero);
properties.position.x = clamp(properties.position.x, originalProperties.position.x
- maxFudgeChange, originalProperties.position.x + maxFudgeChange);
properties.position.y = clamp(properties.position.y, originalProperties.position.y
- maxFudgeChange, originalProperties.position.y + maxFudgeChange);
properties.position.z = clamp(properties.position.z, originalProperties.position.z
- maxFudgeChange, originalProperties.position.z + maxFudgeChange);
}
return properties;
}
filter.wantsOriginalProperties = "position";
filter;

View file

@ -0,0 +1,40 @@
//
// prevent-add-delete-or-edit-of-entities-with-name-of-zone.js
//
//
// Created by Brad Hefta-Gaub to use Entities on Feb. 14, 2018
// Copyright 2018 High Fidelity, Inc.
//
// This sample entity edit filter script will get all edits, adds, physcis, and deletes, but will only block
// deletes, and will pass through all others.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function filter(properties, type, originalProperties, zoneProperties) {
// for adds, check the new properties, if the name is the same as the zone, prevent the add
// note: this case tests the example where originalProperties will be unknown, since as an
// add message, there is no existing entity to get proprties from. But we need to make sure
// zoneProperties is still set in the correct 4th parameter.
if (type == Entities.ADD_FILTER_TYPE) {
if (properties.name == zoneProperties.name) {
return false;
}
} else {
// for edits or deletes, check the "original" property for the entity against the zone's name
if (originalProperties.name == zoneProperties.name) {
return false;
}
}
return properties;
}
filter.wantsToFilterAdd = true; // do run on add
filter.wantsToFilterEdit = true; // do not run on edit
filter.wantsToFilterPhysics = false; // do not run on physics
filter.wantsToFilterDelete = true; // do not run on delete
filter.wantsOriginalProperties = "name";
filter.wantsZoneProperties = "name";
filter;

View file

@ -0,0 +1,26 @@
//
// prevent-add-of-entities-named-bob-example.js
//
//
// Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018
// Copyright 2018 High Fidelity, Inc.
//
// This sample entity edit filter script will get all edits, adds, physcis, and deletes, but will only block
// deletes, and will pass through all others.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function filter(properties, type) {
if (properties.name == "bob") {
return false;
}
return properties;
}
filter.wantsToFilterAdd = true; // do run on add
filter.wantsToFilterEdit = false; // do not run on edit
filter.wantsToFilterPhysics = false; // do not run on physics
filter.wantsToFilterDelete = false; // do not run on delete
filter;

View file

@ -0,0 +1,22 @@
//
// prevent-all-deletes.js
//
//
// Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018
// Copyright 2018 High Fidelity, Inc.
//
// This sample entity edit filter script will prevent deletes of any entities.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function filter() {
return false; // all deletes are blocked
}
filter.wantsToFilterAdd = false; // don't run on adds
filter.wantsToFilterEdit = false; // don't run on edits
filter.wantsToFilterPhysics = false; // don't run on physics
filter.wantsToFilterDelete = true; // do run on deletes
filter;

View file

@ -0,0 +1,24 @@
//
// prevent-delete-in-zone-example.js
//
//
// Created by Brad Hefta-Gaub to use Entities on Jan. 25, 2018
// Copyright 2018 High Fidelity, Inc.
//
// This sample entity edit filter script will get all edits, adds, physcis, and deletes, but will only block
// deletes, and will pass through all others.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function filter(properties, type) {
if (type == Entities.DELETE_FILTER_TYPE) {
return false;
}
return properties;
}
filter.wantsToFilterDelete = true; // do run on deletes
filter;

View file

@ -252,16 +252,17 @@ function onFinishOnOpenClicked() {
}
function signalBodyLoaded() {
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: BODY_LOADED_ACTION
}));
var EVENTBRIDGE_OPEN_DELAY = 500; // Delay required to ensure EventBridge is ready for use.
setTimeout(function () {
EventBridge.scriptEventReceived.connect(onScriptEventReceived);
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: BODY_LOADED_ACTION
}));
}, EVENTBRIDGE_OPEN_DELAY);
}
function onBodyLoaded() {
EventBridge.scriptEventReceived.connect(onScriptEventReceived);
elRecordings = document.getElementById("recordings");
elRecordingsTable = document.getElementById("recordings-table");

View file

@ -139,7 +139,8 @@
}
function setMappingCallback(status) {
if (status !== "") {
// FIXME: "" is for RC <= 63, null is for RC > 63. Remove the former when RC63 is no longer used.
if (status !== null && status !== "") {
error("Error mapping recording to " + mappingPath + " on Asset Server!", status);
return;
}

View file

@ -173,8 +173,18 @@ Laser = function (side) {
// Normal laser operation with trigger.
intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS,
VISIBLE_ONLY);
var tabletIDs = [];
if (HMD.tabletID) {
tabletIDs.push(HMD.tabletID);
}
if (HMD.tabletScreenID) {
tabletIDs.push(HMD.tabletScreenID);
}
if (HMD.homeButtonID) {
tabletIDs.push(HMD.homeButtonID);
}
if (Reticle.pointingAtSystemOverlay || (intersection.overlayID
&& [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID].indexOf(intersection.overlayID) !== -1)) {
&& tabletIDs.indexOf(intersection.overlayID) !== -1)) {
// No laser if pointing at HUD overlay or tablet; system provides lasers for these cases.
if (isLaserOn) {
isLaserOn = false;