diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml
index 96e267f67f..8737e422cb 100644
--- a/interface/resources/qml/Stats.qml
+++ b/interface/resources/qml/Stats.qml
@@ -55,7 +55,11 @@ Item {
text: "Avatars: " + root.avatarCount
}
StatText {
- text: "Frame Rate: " + root.framerate.toFixed(2);
+ text: "Game Rate: " + root.gameLoopRate
+ }
+ StatText {
+ visible: root.expanded
+ text: root.gameUpdateStats
}
StatText {
text: "Render Rate: " + root.renderrate.toFixed(2);
@@ -64,21 +68,17 @@ Item {
text: "Present Rate: " + root.presentrate.toFixed(2);
}
StatText {
- text: "Present New Rate: " + root.presentnewrate.toFixed(2);
+ visible: root.expanded
+ text: " Present New Rate: " + root.presentnewrate.toFixed(2);
}
StatText {
- text: "Present Drop Rate: " + root.presentdroprate.toFixed(2);
+ visible: root.expanded
+ text: " Present Drop Rate: " + root.presentdroprate.toFixed(2);
}
StatText {
text: "Stutter Rate: " + root.stutterrate.toFixed(3);
visible: root.stutterrate != -1;
}
- StatText {
- text: "Simrate: " + root.simrate
- }
- StatText {
- text: "Avatar Simrate: " + root.avatarSimrate
- }
StatText {
text: "Missed Frame Count: " + root.appdropped;
visible: root.appdropped > 0;
@@ -261,9 +261,6 @@ Item {
StatText {
text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
}
- StatText {
- text: "Avatar: " + root.avatarSimulationTime.toFixed(1) + " ms"
- }
StatText {
text: "Triangles: " + root.triangles +
" / Material Switches: " + root.materialSwitches
diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml
index 579b4e7fd6..000e310a66 100644
--- a/interface/resources/qml/desktop/Desktop.qml
+++ b/interface/resources/qml/desktop/Desktop.qml
@@ -298,6 +298,23 @@ FocusScope {
pinned = !pinned
}
+ function isPointOnWindow(point) {
+ for (var i = 0; i < desktop.visibleChildren.length; i++) {
+ var child = desktop.visibleChildren[i];
+ if (child.visible) {
+ if (child.hasOwnProperty("modality")) {
+ var mappedPoint = child.mapFromGlobal(point.x, point.y);
+ var outLine = child.frame.children[2];
+ var framePoint = outLine.mapFromGlobal(point.x, point.y);
+ if (child.contains(mappedPoint) || outLine.contains(framePoint)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
function setPinned(newPinned) {
pinned = newPinned
}
diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
index 182a8df055..77180f7bab 100644
--- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
@@ -26,72 +26,57 @@ Rectangle {
HifiConstants { id: hifi; }
id: root;
+ objectName: "checkout"
property string activeView: "initialize";
property bool purchasesReceived: false;
property bool balanceReceived: false;
- property bool securityImageResultReceived: false;
+ property string itemName;
property string itemId;
- property string itemPreviewImageUrl;
property string itemHref;
property double balanceAfterPurchase;
property bool alreadyOwned: false;
- property int itemPrice: 0;
+ property int itemPrice;
property bool itemIsJson: true;
property bool shouldBuyWithControlledFailure: false;
property bool debugCheckoutSuccess: false;
- property bool canRezCertifiedItems: false;
+ property bool canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified;
// Style
color: hifi.colors.white;
Hifi.QmlCommerce {
id: commerce;
+ onWalletStatusResult: {
+ if (walletStatus === 0) {
+ if (root.activeView !== "needsLogIn") {
+ root.activeView = "needsLogIn";
+ }
+ } else if (walletStatus === 1) {
+ if (root.activeView !== "notSetUp") {
+ root.activeView = "notSetUp";
+ notSetUpTimer.start();
+ }
+ } else if (walletStatus === 2) {
+ if (root.activeView !== "passphraseModal") {
+ root.activeView = "passphraseModal";
+ }
+ } else if (walletStatus === 3) {
+ authSuccessStep();
+ } else {
+ console.log("ERROR in Checkout.qml: Unknown wallet status: " + walletStatus);
+ }
+ }
+
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
- } else if (isLoggedIn) {
- root.activeView = "initialize";
- commerce.account();
- }
- }
-
- onAccountResult: {
- if (result.status === "success") {
- commerce.getKeyFilePathIfExists();
} else {
- // unsure how to handle a failure here. We definitely cannot proceed.
- }
- }
-
- onKeyFilePathIfExistsResult: {
- if (path === "" && root.activeView !== "notSetUp") {
- root.activeView = "notSetUp";
- notSetUpTimer.start();
- } else if (path !== "" && root.activeView === "initialize") {
- commerce.getSecurityImage();
- }
- }
-
- onSecurityImageResult: {
- securityImageResultReceived = true;
- if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
- root.activeView = "notSetUp";
- notSetUpTimer.start();
- } else if (exists && root.activeView === "initialize") {
- commerce.getWalletAuthenticatedStatus();
- }
- }
-
- onWalletAuthenticatedStatusResult: {
- if (!isAuthenticated && root.activeView !== "passphraseModal") {
- root.activeView = "passphraseModal";
- } else if (isAuthenticated) {
- authSuccessStep();
+ commerce.getWalletStatus();
}
}
onBuyResult: {
if (result.status !== 'success') {
- failureErrorText.text = "Here's some more info about the error:
" + (result.message);
+ failureErrorText.text = result.message;
root.activeView = "checkoutFailure";
} else {
root.activeView = "checkoutSuccess";
@@ -123,6 +108,19 @@ Rectangle {
}
}
+ onItemIdChanged: {
+ commerce.inventory();
+ itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
+ }
+
+ onItemHrefChanged: {
+ itemIsJson = root.itemHref.indexOf('.json') !== -1;
+ }
+
+ onItemPriceChanged: {
+ commerce.balance();
+ }
+
Timer {
id: notSetUpTimer;
interval: 200;
@@ -176,6 +174,13 @@ Rectangle {
}
}
}
+ MouseArea {
+ enabled: titleBarContainer.usernameDropdownVisible;
+ anchors.fill: parent;
+ onClicked: {
+ titleBarContainer.usernameDropdownVisible = false;
+ }
+ }
//
// TITLE BAR END
//
@@ -190,10 +195,9 @@ Rectangle {
color: hifi.colors.white;
Component.onCompleted: {
- securityImageResultReceived = false;
purchasesReceived = false;
balanceReceived = false;
- commerce.getLoginStatus();
+ commerce.getWalletStatus();
}
}
@@ -281,7 +285,6 @@ Rectangle {
Image {
id: itemPreviewImage;
- source: root.itemPreviewImageUrl;
anchors.left: parent.left;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
@@ -291,6 +294,7 @@ Rectangle {
RalewaySemiBold {
id: itemNameText;
+ text: root.itemName;
// Text size
size: 26;
// Anchors
@@ -315,19 +319,19 @@ Rectangle {
anchors.top: parent.top;
anchors.right: parent.right;
height: 30;
- width: childrenRect.width;
+ width: itemPriceTextLabel.width + itemPriceText.width + 20;
// "HFC" balance label
HiFiGlyphs {
id: itemPriceTextLabel;
text: hifi.glyphs.hfc;
// Size
- size: 36;
+ size: 30;
// Anchors
anchors.right: itemPriceText.left;
anchors.rightMargin: 4;
anchors.top: parent.top;
- anchors.topMargin: -4;
+ anchors.topMargin: 0;
width: paintedWidth;
height: paintedHeight;
// Style
@@ -335,7 +339,7 @@ Rectangle {
}
FiraSansSemiBold {
id: itemPriceText;
- text: "--";
+ text: root.itemPrice;
// Text size
size: 26;
// Anchors
@@ -405,7 +409,7 @@ Rectangle {
verticalAlignment: Text.AlignTop;
}
- RalewaySemiBold {
+ RalewayRegular {
id: buyText;
// Text size
size: 18;
@@ -424,7 +428,7 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
onLinkActivated: {
- sendToScript({method: 'checkout_goToPurchases', filterText: itemNameText.text});
+ sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
}
}
}
@@ -508,7 +512,7 @@ Rectangle {
RalewaySemiBold {
id: completeText2;
- text: "The item " + '' + itemNameText.text + '' +
+ text: "The item " + '' + root.itemName + '' +
" has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
// Text size
size: 20;
@@ -709,7 +713,9 @@ Rectangle {
anchors.top: titleBarContainer.bottom;
anchors.bottom: root.bottom;
anchors.left: parent.left;
+ anchors.leftMargin: 16;
anchors.right: parent.right;
+ anchors.rightMargin: 16;
RalewayRegular {
id: failureHeaderText;
@@ -718,57 +724,65 @@ Rectangle {
size: 24;
// Anchors
anchors.top: parent.top;
- anchors.topMargin: 80;
+ anchors.topMargin: 40;
height: paintedHeight;
anchors.left: parent.left;
anchors.right: parent.right;
// Style
color: hifi.colors.black;
wrapMode: Text.WordWrap;
- // Alignment
- horizontalAlignment: Text.AlignHCenter;
- verticalAlignment: Text.AlignVCenter;
}
- RalewayRegular {
- id: failureErrorText;
- // Text size
- size: 16;
- // Anchors
+ Rectangle {
+ id: failureErrorTextContainer;
anchors.top: failureHeaderText.bottom;
anchors.topMargin: 35;
- height: paintedHeight;
anchors.left: parent.left;
anchors.right: parent.right;
- // Style
- color: hifi.colors.black;
- wrapMode: Text.WordWrap;
- // Alignment
- horizontalAlignment: Text.AlignHCenter;
- verticalAlignment: Text.AlignVCenter;
+ height: failureErrorText.height + 30;
+ radius: 4;
+ border.width: 2;
+ border.color: "#F3808F";
+ color: "#FFC3CD";
+
+ AnonymousProRegular {
+ id: failureErrorText;
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 15;
+ anchors.left: parent.left;
+ anchors.leftMargin: 8;
+ anchors.right: parent.right;
+ anchors.rightMargin: 8;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.black;
+ wrapMode: Text.Wrap;
+ verticalAlignment: Text.AlignVCenter;
+ }
}
Item {
id: backToMarketplaceButtonContainer;
// Size
width: root.width;
- height: 130;
+ height: 50;
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
- anchors.bottomMargin: 8;
+ anchors.bottomMargin: 16;
// "Back to Marketplace" button
HifiControlsUit.Button {
id: backToMarketplaceButton;
- color: hifi.buttons.black;
+ color: hifi.buttons.noneBorderlessGray;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
- anchors.topMargin: 3;
anchors.bottom: parent.bottom;
- anchors.bottomMargin: 3;
- anchors.right: parent.right;
- anchors.rightMargin: 20;
- width: parent.width/2 - anchors.rightMargin*2;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ width: parent.width/2 - anchors.leftMargin*2;
text: "Back to Marketplace";
onClicked: {
sendToScript({method: 'checkout_continueShopping', itemId: itemId});
@@ -814,15 +828,9 @@ Rectangle {
switch (message.method) {
case 'updateCheckoutQML':
itemId = message.params.itemId;
- itemNameText.text = message.params.itemName;
+ itemName = message.params.itemName;
root.itemPrice = message.params.itemPrice;
- itemPriceText.text = root.itemPrice === 0 ? "Free" : root.itemPrice;
itemHref = message.params.itemHref;
- itemPreviewImageUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
- if (itemHref.indexOf('.json') === -1) {
- root.itemIsJson = false;
- }
- root.canRezCertifiedItems = message.canRezCertifiedItems;
setBuyText();
break;
default:
@@ -845,10 +853,10 @@ Rectangle {
if (root.purchasesReceived && root.balanceReceived) {
if (root.balanceAfterPurchase < 0) {
if (root.alreadyOwned) {
- buyText.text = "Your Wallet does not have sufficient funds to purchase this item again.
" +
- 'View the copy you own in My Purchases';
+ buyText.text = "Your Wallet does not have sufficient funds to purchase this item again.
" +
+ 'View the copy you own in My Purchases';
} else {
- buyText.text = "Your Wallet does not have sufficient funds to purchase this item.";
+ buyText.text = "Your Wallet does not have sufficient funds to purchase this item.";
}
buyTextContainer.color = "#FFC3CD";
buyTextContainer.border.color = "#F3808F";
@@ -856,8 +864,8 @@ Rectangle {
buyGlyph.size = 54;
} else {
if (root.alreadyOwned) {
- buyText.text = 'You already own this item.
Purchasing it will buy another copy.
View this item in My Purchases';
+ buyText.text = 'You already own this item.
Purchasing it will buy another copy.
View this item in My Purchases';
buyTextContainer.color = "#FFD6AD";
buyTextContainer.border.color = "#FAC07D";
buyGlyph.text = hifi.glyphs.alert;
@@ -870,7 +878,7 @@ Rectangle {
buyText.text = "";
}
} else {
- buyText.text = "This Marketplace item isn't an entity. It will not be added to your Purchases.";
+ buyText.text = "This free item will not be added to your Purchases. Non-entities can't yet be purchased for HFC.";
buyTextContainer.color = "#FFD6AD";
buyTextContainer.border.color = "#FAC07D";
buyGlyph.text = hifi.glyphs.alert;
@@ -884,12 +892,6 @@ Rectangle {
} else {
root.activeView = "checkoutSuccess";
}
- if (!balanceReceived) {
- commerce.balance();
- }
- if (!purchasesReceived) {
- commerce.inventory();
- }
}
//
diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml
index 0b236d4566..145b78bdc4 100644
--- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml
+++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml
@@ -82,6 +82,7 @@ Rectangle {
height: 140;
fillMode: Image.PreserveAspectFit;
mipmap: true;
+ cache: false;
}
RalewayRegular {
diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml
index 5786350721..420b51ba15 100644
--- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml
+++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml
@@ -27,23 +27,28 @@ Item {
id: root;
property string referrerURL: "https://metaverse.highfidelity.com/marketplace?";
readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin;
+ property alias usernameDropdownVisible: usernameDropdown.visible;
height: mainContainer.height + additionalDropdownHeight;
Hifi.QmlCommerce {
id: commerce;
- onLoginStatusResult: {
- if (!isLoggedIn) {
+ onWalletStatusResult: {
+ if (walletStatus === 0) {
sendToParent({method: "needsLogIn"});
+ } else if (walletStatus === 3) {
+ commerce.getSecurityImage();
+ } else {
+ console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus);
}
}
- onAccountResult: {
- if (result.status === "success") {
- commerce.getKeyFilePathIfExists();
+ onLoginStatusResult: {
+ if (!isLoggedIn) {
+ sendToParent({method: "needsLogIn"});
} else {
- // unsure how to handle a failure here. We definitely cannot proceed.
+ commerce.getWalletStatus();
}
}
@@ -56,8 +61,7 @@ Item {
}
Component.onCompleted: {
- commerce.getLoginStatus();
- commerce.getSecurityImage();
+ commerce.getWalletStatus();
}
Connections {
@@ -210,6 +214,7 @@ Item {
anchors.bottomMargin: 16;
width: height;
mipmap: true;
+ cache: false;
MouseArea {
enabled: securityImage.visible;
diff --git a/interface/resources/qml/hifi/commerce/common/SortableListModel.qml b/interface/resources/qml/hifi/commerce/common/SortableListModel.qml
new file mode 100644
index 0000000000..2d82e42ddb
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/common/SortableListModel.qml
@@ -0,0 +1,68 @@
+//
+// SortableListModel.qml
+// qml/hifi/commerce/common
+//
+// SortableListModel
+//
+// Created by Zach Fox on 2017-09-28
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+
+ListModel {
+ id: root;
+ property string sortColumnName: "";
+ property bool isSortingDescending: true;
+
+ function swap(a, b) {
+ if (a < b) {
+ move(a, b, 1);
+ move (b - 1, a, 1);
+ } else if (a > b) {
+ move(b, a, 1);
+ move(a - 1, b, 1);
+ }
+ }
+
+ function partition(begin, end, pivot) {
+ var piv = get(pivot)[sortColumnName];
+ swap(pivot, end - 1);
+ var store = begin;
+
+ for (var i = begin; i < end - 1; ++i) {
+ if (isSortingDescending) {
+ if (get(i)[sortColumnName] < piv) {
+ swap(store, i);
+ ++store;
+ }
+ } else {
+ if (get(i)[sortColumnName] > piv) {
+ swap(store, i);
+ ++store;
+ }
+ }
+ }
+ swap(end - 1, store);
+
+ return store;
+ }
+
+ function qsort(begin, end) {
+ if (end - 1 > begin) {
+ var pivot = begin + Math.floor(Math.random() * (end - begin));
+
+ pivot = partition(begin, end, pivot);
+
+ qsort(begin, pivot);
+ qsort(pivot + 1, end);
+ }
+ }
+
+ function quickSort() {
+ qsort(0, count)
+ }
+}
\ No newline at end of file
diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml
index 65bfcfc4b3..19728daa82 100644
--- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml
+++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml
@@ -30,12 +30,20 @@ Rectangle {
property string itemOwner: "--";
property string itemEdition: "--";
property string dateOfPurchase: "";
- property bool closeGoesToPurchases: false;
+ property bool isLightbox: false;
// Style
color: hifi.colors.faintGray;
Hifi.QmlCommerce {
id: commerce;
- }
+ }
+
+ // 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.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ }
Image {
anchors.fill: parent;
@@ -262,7 +270,11 @@ Rectangle {
height: 50;
text: "close";
onClicked: {
- sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases});
+ if (root.isLightbox) {
+ root.visible = false;
+ } else {
+ sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases});
+ }
}
}
@@ -303,7 +315,6 @@ Rectangle {
switch (message.method) {
case 'inspectionCertificate_setMarketplaceId':
root.marketplaceId = message.marketplaceId;
- root.closeGoesToPurchases = message.closeGoesToPurchases;
break;
case 'inspectionCertificate_setItemInfo':
root.itemName = message.itemName;
diff --git a/interface/resources/qml/hifi/commerce/purchases/FirstUseTutorial.qml b/interface/resources/qml/hifi/commerce/purchases/FirstUseTutorial.qml
index b5d7efe818..2e8ad6db65 100644
--- a/interface/resources/qml/hifi/commerce/purchases/FirstUseTutorial.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/FirstUseTutorial.qml
@@ -13,6 +13,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
+import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
@@ -24,40 +25,133 @@ Rectangle {
HifiConstants { id: hifi; }
id: root;
- property string activeView: "step_1";
- // Style
- color: hifi.colors.baseGray;
+ property int activeView: 1;
+
+ Image {
+ anchors.fill: parent;
+ source: "images/Purchase-First-Run-" + root.activeView + ".jpg";
+ }
+
+ // 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.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ }
+
+ Item {
+ id: header;
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: childrenRect.height;
+
+ Image {
+ id: marketplaceHeaderImage;
+ source: "../common/images/marketplaceHeaderImage.png";
+ anchors.top: parent.top;
+ anchors.topMargin: 2;
+ anchors.left: parent.left;
+ anchors.leftMargin: 8;
+ width: 140;
+ height: 58;
+ fillMode: Image.PreserveAspectFit;
+ visible: false;
+ }
+ ColorOverlay {
+ anchors.fill: marketplaceHeaderImage;
+ source: marketplaceHeaderImage;
+ color: "#FFFFFF"
+ }
+ RalewayRegular {
+ id: introText1;
+ text: "INTRODUCTION TO";
+ // Text size
+ size: 15;
+ // Anchors
+ anchors.top: marketplaceHeaderImage.bottom;
+ anchors.topMargin: 8;
+ anchors.left: parent.left;
+ anchors.leftMargin: 12;
+ anchors.right: parent.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.white;
+ }
+ RalewayRegular {
+ id: introText2;
+ text: "My Purchases";
+ // Text size
+ size: 28;
+ // Anchors
+ anchors.top: introText1.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 12;
+ anchors.right: parent.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.white;
+ }
+ }
//
// "STEP 1" START
//
Item {
id: step_1;
- visible: root.activeView === "step_1";
- anchors.top: parent.top;
+ visible: root.activeView === 1;
+ anchors.top: header.bottom;
+ anchors.topMargin: 100;
anchors.left: parent.left;
+ anchors.leftMargin: 30;
anchors.right: parent.right;
- anchors.bottom: tutorialActionButtonsContainer.top;
+ anchors.bottom: parent.bottom;
RalewayRegular {
id: step1text;
- text: "This is the first-time Purchases tutorial.
Here is some bold text " +
- "inside Step 1.";
+ text: "The 'REZ IT' button makes your purchase appear in front of you.";
// Text size
- size: 24;
+ size: 20;
// Anchors
anchors.top: parent.top;
- anchors.bottom: parent.bottom;
anchors.left: parent.left;
- anchors.leftMargin: 16;
+ width: 180;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.white;
+ wrapMode: Text.WordWrap;
+ }
+
+ // "Next" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: step1text.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ width: 150;
+ height: 40;
+ text: "Next";
+ onClicked: {
+ root.activeView++;
+ }
+ }
+
+ // "SKIP" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.noneBorderlessGray;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 32;
anchors.right: parent.right;
anchors.rightMargin: 16;
- // Style
- color: hifi.colors.faintGray;
- wrapMode: Text.WordWrap;
- // Alignment
- horizontalAlignment: Text.AlignHCenter;
- verticalAlignment: Text.AlignVCenter;
+ width: 150;
+ height: 40;
+ text: "SKIP";
+ onClicked: {
+ sendSignalToParent({method: 'tutorial_finished'});
+ }
}
}
//
@@ -69,127 +163,52 @@ Rectangle {
//
Item {
id: step_2;
- visible: root.activeView === "step_2";
- anchors.top: parent.top;
+ visible: root.activeView === 2;
+ anchors.top: header.bottom;
+ anchors.topMargin: 45;
anchors.left: parent.left;
+ anchors.leftMargin: 30;
anchors.right: parent.right;
- anchors.bottom: tutorialActionButtonsContainer.top;
+ anchors.bottom: parent.bottom;
RalewayRegular {
id: step2text;
- text: "STEP TWOOO!!!";
+ text: "If you rez an item twice, the first one will disappear.";
// Text size
- size: 24;
+ size: 20;
// Anchors
anchors.top: parent.top;
- anchors.bottom: parent.bottom;
anchors.left: parent.left;
- anchors.leftMargin: 16;
- anchors.right: parent.right;
- anchors.rightMargin: 16;
+ width: 180;
+ height: paintedHeight;
// Style
- color: hifi.colors.faintGray;
+ color: hifi.colors.white;
wrapMode: Text.WordWrap;
- // Alignment
- horizontalAlignment: Text.AlignHCenter;
- verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "GOT IT" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: step2text.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ width: 150;
+ height: 40;
+ text: "GOT IT";
+ onClicked: {
+ sendSignalToParent({method: 'tutorial_finished'});
+ }
}
}
//
// "STEP 2" END
//
- Item {
- id: tutorialActionButtonsContainer;
- // Size
- width: root.width;
- height: 70;
- // Anchors
- anchors.left: parent.left;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 24;
-
- // "Skip" or "Back" button
- HifiControlsUit.Button {
- id: skipOrBackButton;
- color: hifi.buttons.black;
- colorScheme: hifi.colorSchemes.dark;
- anchors.top: parent.top;
- anchors.topMargin: 3;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 3;
- anchors.left: parent.left;
- anchors.leftMargin: 20;
- width: parent.width/2 - anchors.leftMargin*2;
- text: root.activeView === "step_1" ? "Skip" : "Back";
- onClicked: {
- if (root.activeView === "step_1") {
- sendSignalToParent({method: 'tutorial_skipClicked'});
- } else {
- root.activeView = "step_" + (parseInt(root.activeView.split("_")[1]) - 1);
- }
- }
- }
-
- // "Next" or "Finish" button
- HifiControlsUit.Button {
- id: nextButton;
- color: hifi.buttons.blue;
- colorScheme: hifi.colorSchemes.dark;
- anchors.top: parent.top;
- anchors.topMargin: 3;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 3;
- anchors.right: parent.right;
- anchors.rightMargin: 20;
- width: parent.width/2 - anchors.rightMargin*2;
- text: root.activeView === "step_2" ? "Finish" : "Next";
- onClicked: {
- // If this is the final step...
- if (root.activeView === "step_2") {
- sendSignalToParent({method: 'tutorial_finished'});
- } else {
- root.activeView = "step_" + (parseInt(root.activeView.split("_")[1]) + 1);
- }
- }
- }
- }
-
//
// FUNCTION DEFINITIONS START
//
- //
- // Function Name: fromScript()
- //
- // Relevant Variables:
- // None
- //
- // Arguments:
- // message: The message sent from the JavaScript, in this case the Marketplaces JavaScript.
- // Messages are in format "{method, params}", like json-rpc.
- //
- // Description:
- // Called when a message is received from a script.
- //
- function fromScript(message) {
- switch (message.method) {
- case 'updatePurchases':
- referrerURL = message.referrerURL;
- break;
- case 'purchases_getIsFirstUseResult':
- if (message.isFirstUseOfPurchases && root.activeView !== "firstUseTutorial") {
- root.activeView = "firstUseTutorial";
- } else if (!message.isFirstUseOfPurchases && root.activeView === "initialize") {
- root.activeView = "purchasesMain";
- commerce.inventory();
- }
- break;
- default:
- console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
- }
- }
signal sendSignalToParent(var message);
-
//
// FUNCTION DEFINITIONS END
//
diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
index 1186687a82..a026a818c0 100644
--- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
@@ -34,14 +34,19 @@ Item {
property string itemId;
property string itemPreviewImageUrl;
property string itemHref;
- property int ownedItemCount;
+ property int displayedItemCount;
property int itemEdition;
+ property string originalStatusText;
+ property string originalStatusColor;
+
height: 110;
width: parent.width;
onPurchaseStatusChangedChanged: {
if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
+ root.originalStatusText = statusText.text;
+ root.originalStatusColor = statusText.color;
statusText.text = "CONFIRMED!";
statusText.color = hifi.colors.blueAccent;
confirmedTimer.start();
@@ -53,7 +58,8 @@ Item {
id: confirmedTimer;
interval: 3000;
onTriggered: {
- root.purchaseStatus = "";
+ statusText.text = root.originalStatusText;
+ statusText.color = root.originalStatusColor;
}
}
@@ -174,9 +180,30 @@ Item {
}
Item {
- id: statusContainer;
+ id: editionContainer;
+ visible: root.displayedItemCount > 1 && !statusContainer.visible;
+ anchors.left: itemName.left;
+ anchors.top: certificateContainer.bottom;
+ anchors.topMargin: 8;
+ anchors.bottom: parent.bottom;
+ anchors.right: buttonContainer.left;
+ anchors.rightMargin: 2;
- visible: root.purchaseStatus || root.ownedItemCount > 1;
+ FiraSansRegular {
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ text: "#" + root.itemEdition;
+ size: 15;
+ color: "#cc6a6a6a";
+ verticalAlignment: Text.AlignTop;
+ }
+ }
+
+ Item {
+ id: statusContainer;
+ visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated";
anchors.left: itemName.left;
anchors.top: certificateContainer.bottom;
anchors.topMargin: 8;
@@ -195,8 +222,6 @@ Item {
"PENDING..."
} else if (root.purchaseStatus === "invalidated") {
"INVALIDATED"
- } else if (root.ownedItemCount > 1) {
- "(#" + root.itemEdition + ") You own " + root.ownedItemCount + " others"
} else {
""
}
@@ -207,8 +232,6 @@ Item {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
hifi.colors.redAccent
- } else if (root.ownedItemCount > 1) {
- hifi.colors.blueAccent
} else {
hifi.colors.baseGray
}
@@ -240,8 +263,6 @@ Item {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
hifi.colors.redAccent
- } else if (root.ownedItemCount > 1) {
- hifi.colors.blueAccent
} else {
hifi.colors.baseGray
}
@@ -257,8 +278,6 @@ Item {
sendToPurchases({method: 'showPendingLightbox'});
} else if (root.purchaseStatus === "invalidated") {
sendToPurchases({method: 'showInvalidatedLightbox'});
- } else if (root.ownedItemCount > 1) {
- sendToPurchases({method: 'setFilterText', filterText: root.itemName});
}
}
onEntered: {
@@ -268,9 +287,6 @@ Item {
} else if (root.purchaseStatus === "invalidated") {
statusText.color = hifi.colors.redAccent;
statusIcon.color = hifi.colors.redAccent;
- } else if (root.ownedItemCount > 1) {
- statusText.color = hifi.colors.blueHighlight;
- statusIcon.color = hifi.colors.blueHighlight;
}
}
onExited: {
@@ -280,9 +296,6 @@ Item {
} else if (root.purchaseStatus === "invalidated") {
statusText.color = hifi.colors.redHighlight;
statusIcon.color = hifi.colors.redHighlight;
- } else if (root.ownedItemCount > 1) {
- statusText.color = hifi.colors.blueAccent;
- statusIcon.color = hifi.colors.blueAccent;
}
}
}
diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
index 1002fb881b..0bb1515b69 100644
--- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
@@ -19,6 +19,7 @@ import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../wallet" as HifiWallet
import "../common" as HifiCommerceCommon
+import "../inspectionCertificate" as HifiInspectionCertificate
// references XXX from root context
@@ -31,54 +32,46 @@ Rectangle {
property bool securityImageResultReceived: false;
property bool purchasesReceived: false;
property bool punctuationMode: false;
- property bool canRezCertifiedItems: false;
+ property bool canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified;
property bool pendingInventoryReply: true;
+ property bool isShowingMyItems: false;
+ property bool isDebuggingFirstUseTutorial: false;
// Style
color: hifi.colors.white;
Hifi.QmlCommerce {
id: commerce;
+ onWalletStatusResult: {
+ if (walletStatus === 0) {
+ if (root.activeView !== "needsLogIn") {
+ root.activeView = "needsLogIn";
+ }
+ } else if (walletStatus === 1) {
+ if (root.activeView !== "notSetUp") {
+ root.activeView = "notSetUp";
+ notSetUpTimer.start();
+ }
+ } else if (walletStatus === 2) {
+ if (root.activeView !== "passphraseModal") {
+ root.activeView = "passphraseModal";
+ }
+ } else if (walletStatus === 3) {
+ if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
+ root.activeView = "firstUseTutorial";
+ } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
+ root.activeView = "purchasesMain";
+ commerce.inventory();
+ }
+ } else {
+ console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
+ }
+ }
+
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
- } else if (isLoggedIn) {
- root.activeView = "initialize";
- commerce.account();
- }
- }
-
- onAccountResult: {
- if (result.status === "success") {
- commerce.getKeyFilePathIfExists();
} else {
- // unsure how to handle a failure here. We definitely cannot proceed.
- }
- }
-
- onKeyFilePathIfExistsResult: {
- if (path === "" && root.activeView !== "notSetUp") {
- root.activeView = "notSetUp";
- notSetUpTimer.start();
- } else if (path !== "" && root.activeView === "initialize") {
- commerce.getSecurityImage();
- }
- }
-
- onSecurityImageResult: {
- securityImageResultReceived = true;
- if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
- root.activeView = "notSetUp";
- notSetUpTimer.start();
- } else if (exists && root.activeView === "initialize") {
- commerce.getWalletAuthenticatedStatus();
- }
- }
-
- onWalletAuthenticatedStatusResult: {
- if (!isAuthenticated && root.activeView !== "passphraseModal") {
- root.activeView = "passphraseModal";
- } else if (isAuthenticated) {
- sendToScript({method: 'purchases_getIsFirstUse'});
+ commerce.getWalletStatus();
}
}
@@ -121,6 +114,19 @@ Rectangle {
}
}
+ HifiInspectionCertificate.InspectionCertificate {
+ id: inspectionCertificate;
+ z: 999;
+ visible: false;
+ anchors.fill: parent;
+
+ Connections {
+ onSendToScript: {
+ sendToScript(message);
+ }
+ }
+ }
+
HifiCommerceCommon.CommerceLightbox {
id: lightboxPopup;
visible: false;
@@ -165,6 +171,13 @@ Rectangle {
}
}
}
+ MouseArea {
+ enabled: titleBarContainer.usernameDropdownVisible;
+ anchors.fill: parent;
+ onClicked: {
+ titleBarContainer.usernameDropdownVisible = false;
+ }
+ }
//
// TITLE BAR END
//
@@ -182,7 +195,7 @@ Rectangle {
Component.onCompleted: {
securityImageResultReceived = false;
purchasesReceived = false;
- commerce.getLoginStatus();
+ commerce.getWalletStatus();
}
}
@@ -218,7 +231,7 @@ Rectangle {
onSendSignalToParent: {
if (msg.method === "authSuccess") {
root.activeView = "initialize";
- sendToScript({method: 'purchases_getIsFirstUse'});
+ commerce.getWalletStatus();
} else {
sendToScript(msg);
}
@@ -228,19 +241,16 @@ Rectangle {
FirstUseTutorial {
id: firstUseTutorial;
+ z: 999;
visible: root.activeView === "firstUseTutorial";
- anchors.top: titleBarContainer.bottom;
- anchors.topMargin: -titleBarContainer.additionalDropdownHeight;
- anchors.bottom: parent.bottom;
- anchors.left: parent.left;
- anchors.right: parent.right;
+ anchors.fill: parent;
Connections {
onSendSignalToParent: {
switch (message.method) {
case 'tutorial_skipClicked':
case 'tutorial_finished':
- sendToScript({method: 'purchases_setIsFirstUse'});
+ Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
commerce.inventory();
break;
@@ -278,7 +288,7 @@ Rectangle {
anchors.topMargin: 4;
RalewayRegular {
- id: myPurchasesText;
+ id: myText;
anchors.top: parent.top;
anchors.topMargin: 10;
anchors.bottom: parent.bottom;
@@ -286,7 +296,7 @@ Rectangle {
anchors.left: parent.left;
anchors.leftMargin: 4;
width: paintedWidth;
- text: "My Purchases";
+ text: isShowingMyItems ? "My Items" : "My Purchases";
color: hifi.colors.baseGray;
size: 28;
}
@@ -296,7 +306,7 @@ Rectangle {
colorScheme: hifi.colorSchemes.faintGray;
hasClearButton: true;
hasRoundedBorder: true;
- anchors.left: myPurchasesText.right;
+ anchors.left: myText.right;
anchors.leftMargin: 16;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
@@ -331,7 +341,7 @@ Rectangle {
ListModel {
id: previousPurchasesModel;
}
- ListModel {
+ HifiCommerceCommon.SortableListModel {
id: filteredPurchasesModel;
}
@@ -400,7 +410,7 @@ Rectangle {
ListView {
id: purchasesContentsList;
- visible: purchasesModel.count !== 0;
+ visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
clip: true;
model: filteredPurchasesModel;
// Anchors
@@ -417,6 +427,8 @@ Rectangle {
itemHref: root_file_url;
purchaseStatus: status;
purchaseStatusChanged: statusChanged;
+ itemEdition: model.edition_number;
+ displayedItemCount: model.displayedItemCount;
anchors.topMargin: 12;
anchors.bottomMargin: 12;
@@ -425,6 +437,8 @@ Rectangle {
if (msg.method === 'purchases_itemInfoClicked') {
sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
} else if (msg.method === 'purchases_itemCertificateClicked') {
+ inspectionCertificate.visible = true;
+ inspectionCertificate.isLightbox = true;
sendToScript(msg);
} else if (msg.method === "showInvalidatedLightbox") {
lightboxPopup.titleText = "Item Invalidated";
@@ -448,9 +462,55 @@ Rectangle {
}
}
+ Item {
+ id: noItemsAlertContainer;
+ visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === "";
+ anchors.top: filterBarContainer.bottom;
+ anchors.topMargin: 12;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: parent.width;
+
+ // Explanitory text
+ RalewayRegular {
+ id: noItemsYet;
+ text: "You haven't submitted anything to the Marketplace yet!
Submit an item to the Marketplace to add it to My Items.";
+ // Text size
+ size: 22;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 150;
+ anchors.left: parent.left;
+ anchors.leftMargin: 24;
+ anchors.right: parent.right;
+ anchors.rightMargin: 24;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.baseGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ }
+
+ // "Go To Marketplace" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: noItemsYet.bottom;
+ anchors.topMargin: 20;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ width: parent.width * 2 / 3;
+ height: 50;
+ text: "Visit Marketplace";
+ onClicked: {
+ sendToScript({method: 'purchases_goToMarketplaceClicked'});
+ }
+ }
+ }
+
Item {
id: noPurchasesAlertContainer;
- visible: !purchasesContentsList.visible && root.purchasesReceived;
+ visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === "";
anchors.top: filterBarContainer.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
@@ -478,7 +538,7 @@ Rectangle {
horizontalAlignment: Text.AlignHCenter;
}
- // "Set Up" button
+ // "Go To Marketplace" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
@@ -530,17 +590,43 @@ Rectangle {
// FUNCTION DEFINITIONS START
//
+ function populateDisplayedItemCounts() {
+ var itemCountDictionary = {};
+ var currentItemId;
+ for (var i = 0; i < filteredPurchasesModel.count; i++) {
+ currentItemId = filteredPurchasesModel.get(i).id;
+ if (itemCountDictionary[currentItemId] === undefined) {
+ itemCountDictionary[currentItemId] = 1;
+ } else {
+ itemCountDictionary[currentItemId]++;
+ }
+ }
+
+ for (var i = 0; i < filteredPurchasesModel.count; i++) {
+ filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]);
+ }
+ }
+
+ function sortByDate() {
+ filteredPurchasesModel.sortColumnName = "purchase_date";
+ filteredPurchasesModel.isSortingDescending = false;
+ filteredPurchasesModel.quickSort();
+ }
+
function buildFilteredPurchasesModel() {
filteredPurchasesModel.clear();
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
- if (purchasesModel.get(i).status !== "confirmed") {
+ if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
filteredPurchasesModel.insert(0, purchasesModel.get(i));
- } else {
+ } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === -1) || !root.isShowingMyItems) {
filteredPurchasesModel.append(purchasesModel.get(i));
}
}
}
+
+ populateDisplayedItemCounts();
+ sortByDate();
}
function checkIfAnyItemStatusChanged() {
@@ -581,16 +667,14 @@ Rectangle {
case 'updatePurchases':
referrerURL = message.referrerURL;
titleBarContainer.referrerURL = message.referrerURL;
- root.canRezCertifiedItems = message.canRezCertifiedItems;
filterBar.text = message.filterText ? message.filterText : "";
break;
- case 'purchases_getIsFirstUseResult':
- if (message.isFirstUseOfPurchases && root.activeView !== "firstUseTutorial") {
- root.activeView = "firstUseTutorial";
- } else if (!message.isFirstUseOfPurchases && root.activeView === "initialize") {
- root.activeView = "purchasesMain";
- commerce.inventory();
- }
+ case 'inspectionCertificate_setMarketplaceId':
+ case 'inspectionCertificate_setItemInfo':
+ inspectionCertificate.fromScript(message);
+ break;
+ case 'purchases_showMyItems':
+ root.isShowingMyItems = true;
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
diff --git a/interface/resources/qml/hifi/commerce/purchases/images/Purchase-First-Run-1.jpg b/interface/resources/qml/hifi/commerce/purchases/images/Purchase-First-Run-1.jpg
new file mode 100644
index 0000000000..ce0d87363f
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/purchases/images/Purchase-First-Run-1.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/purchases/images/Purchase-First-Run-2.jpg b/interface/resources/qml/hifi/commerce/purchases/images/Purchase-First-Run-2.jpg
new file mode 100644
index 0000000000..40bed974af
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/purchases/images/Purchase-First-Run-2.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml
index e560384807..a75d511793 100644
--- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml
@@ -90,7 +90,7 @@ Item {
} else {
// Error submitting new passphrase
resetSubmitButton();
- passphraseSelection.setErrorText("Backend error");
+ passphraseSelection.setErrorText("Current passphrase incorrect - try again");
}
} else {
sendSignalToWallet(msg);
@@ -137,9 +137,10 @@ Item {
width: 150;
text: "Submit";
onClicked: {
- if (passphraseSelection.validateAndSubmitPassphrase()) {
- passphraseSubmitButton.text = "Submitting...";
- passphraseSubmitButton.enabled = false;
+ passphraseSubmitButton.text = "Submitting...";
+ passphraseSubmitButton.enabled = false;
+ if (!passphraseSelection.validateAndSubmitPassphrase()) {
+ resetSubmitButton();
}
}
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml
index 8ab0c3af60..5bd88ba790 100644
--- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml
@@ -129,6 +129,7 @@ Item {
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
+ cache: false;
MouseArea {
enabled: titleBarSecurityImage.visible;
diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml
index f0338bf1f1..7c0cecd98d 100644
--- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml
@@ -26,6 +26,7 @@ Item {
id: root;
property bool isChangingPassphrase: false;
property bool isShowingTip: false;
+ property bool shouldImmediatelyFocus: true;
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
@@ -42,8 +43,8 @@ Item {
passphrasePageSecurityImage.source = "image://security/securityImage";
}
- onWalletAuthenticatedStatusResult: {
- sendMessageToLightbox({method: 'statusResult', status: isAuthenticated});
+ onChangePassphraseStatusResult: {
+ sendMessageToLightbox({method: 'statusResult', status: changeSuccess});
}
}
@@ -53,10 +54,8 @@ Item {
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
- if (root.isChangingPassphrase) {
- currentPassphraseField.focus = true;
- } else {
- passphraseField.focus = true;
+ if (root.shouldImmediatelyFocus) {
+ focusFirstTextField();
}
sendMessageToLightbox({method: 'disableHmdPreview'});
} else {
@@ -311,7 +310,7 @@ Item {
passphraseFieldAgain.error = false;
currentPassphraseField.error = false;
setErrorText("");
- commerce.setPassphrase(passphraseField.text);
+ commerce.changePassphrase(currentPassphraseField.text, passphraseField.text);
return true;
}
}
@@ -327,5 +326,13 @@ Item {
setErrorText("");
}
+ function focusFirstTextField() {
+ if (root.isChangingPassphrase) {
+ currentPassphraseField.focus = true;
+ } else {
+ passphraseField.focus = true;
+ }
+ }
+
signal sendMessageToLightbox(var msg);
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml
index fcce798646..9b70bb1f71 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Security.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml
@@ -280,6 +280,34 @@ Item {
verticalAlignment: Text.AlignVCenter;
}
+ Rectangle {
+ id: removeHmdContainer;
+ z: 998;
+ visible: false;
+ color: hifi.colors.blueHighlight;
+ anchors.fill: backupInstructionsButton;
+ radius: 5;
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ }
+
+ RalewayBold {
+ anchors.fill: parent;
+ text: "INSTRUCTIONS OPEN ON DESKTOP";
+ size: 15;
+ color: hifi.colors.white;
+ verticalAlignment: Text.AlignVCenter;
+ horizontalAlignment: Text.AlignHCenter;
+ }
+
+ Timer {
+ id: removeHmdContainerTimer;
+ interval: 5000;
+ onTriggered: removeHmdContainer.visible = false
+ }
+ }
+
HifiControlsUit.Button {
id: backupInstructionsButton;
text: "View Backup Instructions";
@@ -293,6 +321,9 @@ Item {
onClicked: {
Qt.openUrlExternally("https://www.highfidelity.com/");
+ Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')));
+ removeHmdContainer.visible = true;
+ removeHmdContainerTimer.start();
}
}
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml
index 706684ed39..e12332cd0c 100644
--- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml
@@ -65,6 +65,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
+ cache: false;
}
}
MouseArea {
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index 4cc1f2f8ec..9056d5bed3 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -38,48 +38,41 @@ Rectangle {
Hifi.QmlCommerce {
id: commerce;
+ onWalletStatusResult: {
+ if (walletStatus === 0) {
+ if (root.activeView !== "needsLogIn") {
+ root.activeView = "needsLogIn";
+ }
+ } else if (walletStatus === 1) {
+ if (root.activeView !== "walletSetup") {
+ root.activeView = "walletSetup";
+ }
+ } else if (walletStatus === 2) {
+ if (root.activeView !== "passphraseModal") {
+ root.activeView = "passphraseModal";
+ }
+ } else if (walletStatus === 3) {
+ root.activeView = "walletHome";
+ commerce.getSecurityImage();
+ } else {
+ console.log("ERROR in Wallet.qml: Unknown wallet status: " + walletStatus);
+ }
+ }
+
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
- root.activeView = "initialize";
- commerce.account();
- }
- }
-
- onAccountResult: {
- if (result.status === "success") {
- commerce.getKeyFilePathIfExists();
- } else {
- // unsure how to handle a failure here. We definitely cannot proceed.
- }
- }
-
- onKeyFilePathIfExistsResult: {
- if (path === "" && root.activeView !== "walletSetup") {
- root.activeView = "walletSetup";
- } else if (path !== "" && root.activeView === "initialize") {
- commerce.getSecurityImage();
+ commerce.getWalletStatus();
}
}
onSecurityImageResult: {
- if (!exists && root.activeView !== "walletSetup") { // "If security image is not set up"
- root.activeView = "walletSetup";
- } else if (exists && root.activeView === "initialize") {
- commerce.getWalletAuthenticatedStatus();
+ if (exists) {
titleBarSecurityImage.source = "";
titleBarSecurityImage.source = "image://security/securityImage";
}
}
-
- onWalletAuthenticatedStatusResult: {
- if (!isAuthenticated && passphraseModal && root.activeView !== "passphraseModal") {
- root.activeView = "passphraseModal";
- } else if (isAuthenticated) {
- root.activeView = "walletHome";
- }
- }
}
SecurityImageModel {
@@ -149,6 +142,7 @@ Rectangle {
anchors.bottomMargin: 6;
width: height;
mipmap: true;
+ cache: false;
MouseArea {
enabled: titleBarSecurityImage.visible;
@@ -179,7 +173,7 @@ Rectangle {
if (msg.method === 'walletSetup_finished') {
if (msg.referrer === '') {
root.activeView = "initialize";
- commerce.getLoginStatus();
+ commerce.getWalletStatus();
} else if (msg.referrer === 'purchases') {
sendToScript({method: 'goToPurchases'});
}
@@ -254,7 +248,7 @@ Rectangle {
color: hifi.colors.baseGray;
Component.onCompleted: {
- commerce.getLoginStatus();
+ commerce.getWalletStatus();
}
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
index 7083592c1d..b94616bd7a 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -26,6 +26,7 @@ Item {
id: root;
property bool historyReceived: false;
+ property int pendingCount: 0;
Hifi.QmlCommerce {
id: commerce;
@@ -39,6 +40,8 @@ Item {
if (result.status === 'success') {
transactionHistoryModel.clear();
transactionHistoryModel.append(result.data.history);
+
+ calculatePendingAndInvalidated();
}
}
}
@@ -200,55 +203,74 @@ Item {
model: transactionHistoryModel;
delegate: Item {
width: parent.width;
- height: transactionText.height + 30;
+ height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
- HifiControlsUit.Separator {
- visible: index === 0;
- colorScheme: 1;
- anchors.left: parent.left;
- anchors.right: parent.right;
- anchors.top: parent.top;
- }
-
- AnonymousProRegular {
- id: dateText;
- text: getFormattedDate(model.created_at * 1000);
- // Style
- size: 18;
+ Item {
+ visible: model.transaction_type === "pendingCount" && root.pendingCount !== 0;
+ anchors.top: parent.top;
anchors.left: parent.left;
- anchors.top: parent.top;
- anchors.topMargin: 15;
- width: 118;
- height: paintedHeight;
- color: hifi.colors.blueAccent;
- wrapMode: Text.WordWrap;
- // Alignment
- horizontalAlignment: Text.AlignRight;
- }
+ width: parent.width;
+ height: visible ? parent.height : 0;
- AnonymousProRegular {
- id: transactionText;
- text: model.text;
- size: 18;
- anchors.top: parent.top;
- anchors.topMargin: 15;
- anchors.left: dateText.right;
- anchors.leftMargin: 20;
- anchors.right: parent.right;
- height: paintedHeight;
- color: hifi.colors.baseGrayHighlight;
- wrapMode: Text.WordWrap;
-
- onLinkActivated: {
- sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
+ AnonymousProRegular {
+ id: pendingCountText;
+ anchors.fill: parent;
+ text: root.pendingCount + ' Transaction' + (root.pendingCount > 1 ? 's' : '') + ' Pending';
+ size: 18;
+ color: hifi.colors.blueAccent;
+ verticalAlignment: Text.AlignVCenter;
+ horizontalAlignment: Text.AlignHCenter;
}
}
- HifiControlsUit.Separator {
- colorScheme: 1;
+ Item {
+ visible: model.transaction_type !== "pendingCount" && (model.status === "confirmed" || model.status === "invalidated");
+ anchors.top: parent.top;
anchors.left: parent.left;
- anchors.right: parent.right;
- anchors.bottom: parent.bottom;
+ width: parent.width;
+ height: visible ? parent.height : 0;
+
+ AnonymousProRegular {
+ id: dateText;
+ text: model.created_at ? getFormattedDate(model.created_at * 1000) : "";
+ // Style
+ size: 18;
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+ anchors.topMargin: 15;
+ width: 118;
+ height: paintedHeight;
+ color: hifi.colors.blueAccent;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignRight;
+ }
+
+ AnonymousProRegular {
+ id: transactionText;
+ text: model.text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.text) : model.text) : "";
+ size: 18;
+ anchors.top: parent.top;
+ anchors.topMargin: 15;
+ anchors.left: dateText.right;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ height: paintedHeight;
+ color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight;
+ wrapMode: Text.WordWrap;
+ font.strikeout: model.status === "invalidated";
+
+ onLinkActivated: {
+ sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
+ }
+ }
+
+ HifiControlsUit.Separator {
+ colorScheme: 1;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ }
}
}
onAtYEndChanged: {
@@ -299,6 +321,19 @@ Item {
return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm;
}
+
+ function calculatePendingAndInvalidated(startingPendingCount) {
+ var pendingCount = startingPendingCount ? startingPendingCount : 0;
+ for (var i = 0; i < transactionHistoryModel.count; i++) {
+ if (transactionHistoryModel.get(i).status === "pending") {
+ pendingCount++;
+ }
+ }
+
+ root.pendingCount = pendingCount;
+ transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
+ }
+
//
// Function Name: fromScript()
//
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml
index c2b20a37ef..898cdf0ef2 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml
@@ -43,7 +43,7 @@ Item {
if (!exists && root.lastPage === "step_2") {
// ERROR! Invalid security image.
root.activeView = "step_2";
- } else {
+ } else if (exists) {
titleBarSecurityImage.source = "";
titleBarSecurityImage.source = "image://security/securityImage";
}
@@ -116,7 +116,7 @@ Item {
Image {
id: titleBarSecurityImage;
source: "";
- visible: !securityImageTip.visible && titleBarSecurityImage.source !== "";
+ visible: !securityImageTip.visible && titleBarSecurityImage.source !== "" && root.activeView !== "step_1" && root.activeView !== "step_2";
anchors.right: parent.right;
anchors.rightMargin: 6;
anchors.top: parent.top;
@@ -125,6 +125,7 @@ Item {
anchors.bottomMargin: 6;
width: height;
mipmap: true;
+ cache: false;
MouseArea {
enabled: titleBarSecurityImage.visible;
@@ -422,6 +423,7 @@ Item {
onClicked: {
root.hasShownSecurityImageTip = true;
securityImageTip.visible = false;
+ passphraseSelection.focusFirstTextField();
}
}
}
@@ -466,6 +468,7 @@ Item {
PassphraseSelection {
id: passphraseSelection;
+ shouldImmediatelyFocus: root.hasShownSecurityImageTip;
isShowingTip: securityImageTip.visible;
anchors.top: passphraseTitleHelper.bottom;
anchors.topMargin: 30;
@@ -680,6 +683,7 @@ Item {
instructions02Container.visible = true;
keysReadyPageFinishButton.visible = true;
Qt.openUrlExternally("https://www.highfidelity.com/");
+ Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')));
}
}
}
diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml
index 030af974f6..c3b8399e01 100644
--- a/interface/resources/qml/windows/Frame.qml
+++ b/interface/resources/qml/windows/Frame.qml
@@ -26,6 +26,7 @@ Item {
readonly property int frameMarginRight: frame.decoration ? frame.decoration.frameMarginRight : 0
readonly property int frameMarginTop: frame.decoration ? frame.decoration.frameMarginTop : 0
readonly property int frameMarginBottom: frame.decoration ? frame.decoration.frameMarginBottom : 0
+ readonly property int offsetCorrection: 20
// Frames always fill their parents, but their decorations may extend
// beyond the window via negative margin sizes
@@ -73,7 +74,7 @@ Item {
Rectangle {
id: sizeOutline
x: -frameMarginLeft
- y: -frameMarginTop
+ y: -frameMarginTop - offsetCorrection
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
color: hifi.colors.baseGrayHighlight15
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 5f5896a978..49d8b505d5 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -167,6 +167,7 @@
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/RatesScriptingInterface.h"
#include "scripting/SelectionScriptingInterface.h"
+#include "scripting/WalletScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
#endif
@@ -686,6 +687,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set();
DependencyManager::set();
DependencyManager::set();
+ DependencyManager::set();
DependencyManager::set();
@@ -1544,15 +1546,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto displayPlugin = qApp->getActiveDisplayPlugin();
- properties["fps"] = _frameCounter.rate();
- properties["target_frame_rate"] = getTargetFrameRate();
- properties["render_rate"] = displayPlugin->renderRate();
+ properties["render_rate"] = _renderLoopCounter.rate();
+ properties["target_render_rate"] = getTargetRenderFrameRate();
properties["present_rate"] = displayPlugin->presentRate();
properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate();
properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate();
properties["stutter_rate"] = displayPlugin->stutterRate();
- properties["sim_rate"] = getAverageSimsPerSecond();
- properties["avatar_sim_rate"] = getAvatarSimrate();
+ properties["game_rate"] = getGameLoopRate();
properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection();
properties["hardware_stats"] = displayPlugin->getHardwareStats();
@@ -2331,6 +2331,7 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("Selection", DependencyManager::get().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get().data());
+ surfaceContext->setContextProperty("Wallet", DependencyManager::get().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
@@ -2475,7 +2476,7 @@ void Application::updateCamera(RenderArgs& renderArgs) {
}
// Update camera position
if (!isHMDMode()) {
- _myCamera.update(1.0f / _frameCounter.rate());
+ _myCamera.update();
}
renderArgs._cameraMode = (int8_t)_myCamera.getMode();
@@ -3843,7 +3844,7 @@ void Application::idle() {
if (displayPlugin) {
PROFILE_COUNTER_IF_CHANGED(app, "present", float, displayPlugin->presentRate());
}
- PROFILE_COUNTER_IF_CHANGED(app, "fps", float, _frameCounter.rate());
+ PROFILE_COUNTER_IF_CHANGED(app, "renderLoopRate", float, _renderLoopCounter.rate());
PROFILE_COUNTER_IF_CHANGED(app, "currentDownloads", int, ResourceCache::getLoadingRequests().length());
PROFILE_COUNTER_IF_CHANGED(app, "pendingDownloads", int, ResourceCache::getPendingRequestCount());
PROFILE_COUNTER_IF_CHANGED(app, "currentProcessing", int, DependencyManager::get()->getStat("Processing").toInt());
@@ -3879,8 +3880,6 @@ void Application::idle() {
Stats::getInstance()->updateStats();
- _simCounter.increment();
-
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
// details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing
// details normally.
@@ -3960,6 +3959,7 @@ void Application::idle() {
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
cameraMenuChanged();
}
+ _gameLoopCounter.increment();
}
ivec2 Application::getMouse() const {
@@ -4646,12 +4646,7 @@ static bool domainLoadingInProgress = false;
void Application::update(float deltaTime) {
- PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1);
-
- bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
- PerformanceWarning warn(showWarnings, "Application::update()");
-
- updateLOD(deltaTime);
+ PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_renderFrameCount + 1);
if (!_physicsEnabled) {
if (!domainLoadingInProgress) {
@@ -4682,6 +4677,7 @@ void Application::update(float deltaTime) {
PROFILE_ASYNC_END(app, "Scene Loading", "");
}
+ auto myAvatar = getMyAvatar();
{
PerformanceTimer perfTimer("devices");
@@ -4715,129 +4711,127 @@ void Application::update(float deltaTime) {
_lastFaceTrackerUpdate = 0;
}
- }
+ auto userInputMapper = DependencyManager::get();
- auto myAvatar = getMyAvatar();
- auto userInputMapper = DependencyManager::get();
+ controller::InputCalibrationData calibrationData = {
+ myAvatar->getSensorToWorldMatrix(),
+ createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()),
+ myAvatar->getHMDSensorMatrix(),
+ myAvatar->getCenterEyeCalibrationMat(),
+ myAvatar->getHeadCalibrationMat(),
+ myAvatar->getSpine2CalibrationMat(),
+ myAvatar->getHipsCalibrationMat(),
+ myAvatar->getLeftFootCalibrationMat(),
+ myAvatar->getRightFootCalibrationMat(),
+ myAvatar->getRightArmCalibrationMat(),
+ myAvatar->getLeftArmCalibrationMat(),
+ myAvatar->getRightHandCalibrationMat(),
+ myAvatar->getLeftHandCalibrationMat()
+ };
- controller::InputCalibrationData calibrationData = {
- myAvatar->getSensorToWorldMatrix(),
- createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()),
- myAvatar->getHMDSensorMatrix(),
- myAvatar->getCenterEyeCalibrationMat(),
- myAvatar->getHeadCalibrationMat(),
- myAvatar->getSpine2CalibrationMat(),
- myAvatar->getHipsCalibrationMat(),
- myAvatar->getLeftFootCalibrationMat(),
- myAvatar->getRightFootCalibrationMat(),
- myAvatar->getRightArmCalibrationMat(),
- myAvatar->getLeftArmCalibrationMat(),
- myAvatar->getRightHandCalibrationMat(),
- myAvatar->getLeftHandCalibrationMat()
- };
-
- InputPluginPointer keyboardMousePlugin;
- for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
- if (inputPlugin->getName() == KeyboardMouseDevice::NAME) {
- keyboardMousePlugin = inputPlugin;
- } else if (inputPlugin->isActive()) {
- inputPlugin->pluginUpdate(deltaTime, calibrationData);
- }
- }
-
- userInputMapper->setInputCalibrationData(calibrationData);
- userInputMapper->update(deltaTime);
-
- if (keyboardMousePlugin && keyboardMousePlugin->isActive()) {
- keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData);
- }
-
- // Transfer the user inputs to the driveKeys
- // FIXME can we drop drive keys and just have the avatar read the action states directly?
- myAvatar->clearDriveKeys();
- if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
- if (!_controllerScriptingInterface->areActionsCaptured()) {
- myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
- myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
- myAvatar->setDriveKey(MyAvatar::TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
- if (deltaTime > FLT_EPSILON) {
- myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH));
- myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW));
- myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW));
+ InputPluginPointer keyboardMousePlugin;
+ for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
+ if (inputPlugin->getName() == KeyboardMouseDevice::NAME) {
+ keyboardMousePlugin = inputPlugin;
+ } else if (inputPlugin->isActive()) {
+ inputPlugin->pluginUpdate(deltaTime, calibrationData);
}
}
- myAvatar->setDriveKey(MyAvatar::ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
- }
- static const std::vector avatarControllerActions = {
- controller::Action::LEFT_HAND,
- controller::Action::RIGHT_HAND,
- controller::Action::LEFT_FOOT,
- controller::Action::RIGHT_FOOT,
- controller::Action::HIPS,
- controller::Action::SPINE2,
- controller::Action::HEAD,
- controller::Action::LEFT_HAND_THUMB1,
- controller::Action::LEFT_HAND_THUMB2,
- controller::Action::LEFT_HAND_THUMB3,
- controller::Action::LEFT_HAND_THUMB4,
- controller::Action::LEFT_HAND_INDEX1,
- controller::Action::LEFT_HAND_INDEX2,
- controller::Action::LEFT_HAND_INDEX3,
- controller::Action::LEFT_HAND_INDEX4,
- controller::Action::LEFT_HAND_MIDDLE1,
- controller::Action::LEFT_HAND_MIDDLE2,
- controller::Action::LEFT_HAND_MIDDLE3,
- controller::Action::LEFT_HAND_MIDDLE4,
- controller::Action::LEFT_HAND_RING1,
- controller::Action::LEFT_HAND_RING2,
- controller::Action::LEFT_HAND_RING3,
- controller::Action::LEFT_HAND_RING4,
- controller::Action::LEFT_HAND_PINKY1,
- controller::Action::LEFT_HAND_PINKY2,
- controller::Action::LEFT_HAND_PINKY3,
- controller::Action::LEFT_HAND_PINKY4,
- controller::Action::RIGHT_HAND_THUMB1,
- controller::Action::RIGHT_HAND_THUMB2,
- controller::Action::RIGHT_HAND_THUMB3,
- controller::Action::RIGHT_HAND_THUMB4,
- controller::Action::RIGHT_HAND_INDEX1,
- controller::Action::RIGHT_HAND_INDEX2,
- controller::Action::RIGHT_HAND_INDEX3,
- controller::Action::RIGHT_HAND_INDEX4,
- controller::Action::RIGHT_HAND_MIDDLE1,
- controller::Action::RIGHT_HAND_MIDDLE2,
- controller::Action::RIGHT_HAND_MIDDLE3,
- controller::Action::RIGHT_HAND_MIDDLE4,
- controller::Action::RIGHT_HAND_RING1,
- controller::Action::RIGHT_HAND_RING2,
- controller::Action::RIGHT_HAND_RING3,
- controller::Action::RIGHT_HAND_RING4,
- controller::Action::RIGHT_HAND_PINKY1,
- controller::Action::RIGHT_HAND_PINKY2,
- controller::Action::RIGHT_HAND_PINKY3,
- controller::Action::RIGHT_HAND_PINKY4,
- controller::Action::LEFT_ARM,
- controller::Action::RIGHT_ARM,
- controller::Action::LEFT_SHOULDER,
- controller::Action::RIGHT_SHOULDER,
- controller::Action::LEFT_FORE_ARM,
- controller::Action::RIGHT_FORE_ARM,
- controller::Action::LEFT_LEG,
- controller::Action::RIGHT_LEG,
- controller::Action::LEFT_UP_LEG,
- controller::Action::RIGHT_UP_LEG,
- controller::Action::LEFT_TOE_BASE,
- controller::Action::RIGHT_TOE_BASE
- };
+ userInputMapper->setInputCalibrationData(calibrationData);
+ userInputMapper->update(deltaTime);
- // copy controller poses from userInputMapper to myAvatar.
- glm::mat4 myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
- glm::mat4 worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix());
- glm::mat4 avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
- for (auto& action : avatarControllerActions) {
- controller::Pose pose = userInputMapper->getPoseState(action);
- myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix));
+ if (keyboardMousePlugin && keyboardMousePlugin->isActive()) {
+ keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData);
+ }
+
+ // Transfer the user inputs to the driveKeys
+ // FIXME can we drop drive keys and just have the avatar read the action states directly?
+ myAvatar->clearDriveKeys();
+ if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
+ if (!_controllerScriptingInterface->areActionsCaptured()) {
+ myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
+ myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
+ myAvatar->setDriveKey(MyAvatar::TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
+ if (deltaTime > FLT_EPSILON) {
+ myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH));
+ myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW));
+ myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW));
+ }
+ }
+ myAvatar->setDriveKey(MyAvatar::ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
+ }
+
+ static const std::vector avatarControllerActions = {
+ controller::Action::LEFT_HAND,
+ controller::Action::RIGHT_HAND,
+ controller::Action::LEFT_FOOT,
+ controller::Action::RIGHT_FOOT,
+ controller::Action::HIPS,
+ controller::Action::SPINE2,
+ controller::Action::HEAD,
+ controller::Action::LEFT_HAND_THUMB1,
+ controller::Action::LEFT_HAND_THUMB2,
+ controller::Action::LEFT_HAND_THUMB3,
+ controller::Action::LEFT_HAND_THUMB4,
+ controller::Action::LEFT_HAND_INDEX1,
+ controller::Action::LEFT_HAND_INDEX2,
+ controller::Action::LEFT_HAND_INDEX3,
+ controller::Action::LEFT_HAND_INDEX4,
+ controller::Action::LEFT_HAND_MIDDLE1,
+ controller::Action::LEFT_HAND_MIDDLE2,
+ controller::Action::LEFT_HAND_MIDDLE3,
+ controller::Action::LEFT_HAND_MIDDLE4,
+ controller::Action::LEFT_HAND_RING1,
+ controller::Action::LEFT_HAND_RING2,
+ controller::Action::LEFT_HAND_RING3,
+ controller::Action::LEFT_HAND_RING4,
+ controller::Action::LEFT_HAND_PINKY1,
+ controller::Action::LEFT_HAND_PINKY2,
+ controller::Action::LEFT_HAND_PINKY3,
+ controller::Action::LEFT_HAND_PINKY4,
+ controller::Action::RIGHT_HAND_THUMB1,
+ controller::Action::RIGHT_HAND_THUMB2,
+ controller::Action::RIGHT_HAND_THUMB3,
+ controller::Action::RIGHT_HAND_THUMB4,
+ controller::Action::RIGHT_HAND_INDEX1,
+ controller::Action::RIGHT_HAND_INDEX2,
+ controller::Action::RIGHT_HAND_INDEX3,
+ controller::Action::RIGHT_HAND_INDEX4,
+ controller::Action::RIGHT_HAND_MIDDLE1,
+ controller::Action::RIGHT_HAND_MIDDLE2,
+ controller::Action::RIGHT_HAND_MIDDLE3,
+ controller::Action::RIGHT_HAND_MIDDLE4,
+ controller::Action::RIGHT_HAND_RING1,
+ controller::Action::RIGHT_HAND_RING2,
+ controller::Action::RIGHT_HAND_RING3,
+ controller::Action::RIGHT_HAND_RING4,
+ controller::Action::RIGHT_HAND_PINKY1,
+ controller::Action::RIGHT_HAND_PINKY2,
+ controller::Action::RIGHT_HAND_PINKY3,
+ controller::Action::RIGHT_HAND_PINKY4,
+ controller::Action::LEFT_ARM,
+ controller::Action::RIGHT_ARM,
+ controller::Action::LEFT_SHOULDER,
+ controller::Action::RIGHT_SHOULDER,
+ controller::Action::LEFT_FORE_ARM,
+ controller::Action::RIGHT_FORE_ARM,
+ controller::Action::LEFT_LEG,
+ controller::Action::RIGHT_LEG,
+ controller::Action::LEFT_UP_LEG,
+ controller::Action::RIGHT_UP_LEG,
+ controller::Action::LEFT_TOE_BASE,
+ controller::Action::RIGHT_TOE_BASE
+ };
+
+ // copy controller poses from userInputMapper to myAvatar.
+ glm::mat4 myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
+ glm::mat4 worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix());
+ glm::mat4 avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
+ for (auto& action : avatarControllerActions) {
+ controller::Pose pose = userInputMapper->getPoseState(action);
+ myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix));
+ }
}
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
@@ -4845,117 +4839,123 @@ void Application::update(float deltaTime) {
QSharedPointer avatarManager = DependencyManager::get();
- if (_physicsEnabled) {
+ {
PROFILE_RANGE_EX(simulation_physics, "Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
-
PerformanceTimer perfTimer("physics");
+ if (_physicsEnabled) {
+ {
+ PROFILE_RANGE_EX(simulation_physics, "UpdateStates", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
- {
- PROFILE_RANGE_EX(simulation_physics, "UpdateStates", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
+ PerformanceTimer perfTimer("updateStates)");
+ static VectorOfMotionStates motionStates;
+ _entitySimulation->getObjectsToRemoveFromPhysics(motionStates);
+ _physicsEngine->removeObjects(motionStates);
+ _entitySimulation->deleteObjectsRemovedFromPhysics();
- PerformanceTimer perfTimer("updateStates)");
- static VectorOfMotionStates motionStates;
- _entitySimulation->getObjectsToRemoveFromPhysics(motionStates);
- _physicsEngine->removeObjects(motionStates);
- _entitySimulation->deleteObjectsRemovedFromPhysics();
+ getEntities()->getTree()->withReadLock([&] {
+ _entitySimulation->getObjectsToAddToPhysics(motionStates);
+ _physicsEngine->addObjects(motionStates);
- getEntities()->getTree()->withReadLock([&] {
- _entitySimulation->getObjectsToAddToPhysics(motionStates);
- _physicsEngine->addObjects(motionStates);
-
- });
- getEntities()->getTree()->withReadLock([&] {
- _entitySimulation->getObjectsToChange(motionStates);
- VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
- _entitySimulation->setObjectsToChange(stillNeedChange);
- });
-
- _entitySimulation->applyDynamicChanges();
-
- avatarManager->getObjectsToRemoveFromPhysics(motionStates);
- _physicsEngine->removeObjects(motionStates);
- avatarManager->getObjectsToAddToPhysics(motionStates);
- _physicsEngine->addObjects(motionStates);
- avatarManager->getObjectsToChange(motionStates);
- _physicsEngine->changeObjects(motionStates);
-
- myAvatar->prepareForPhysicsSimulation();
- _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
- dynamic->prepareForPhysicsSimulation();
- });
- }
- {
- PROFILE_RANGE_EX(simulation_physics, "StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount());
- PerformanceTimer perfTimer("stepSimulation");
- getEntities()->getTree()->withWriteLock([&] {
- _physicsEngine->stepSimulation();
- });
- }
- {
- PROFILE_RANGE_EX(simulation_physics, "HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
- PerformanceTimer perfTimer("harvestChanges");
- if (_physicsEngine->hasOutgoingChanges()) {
- // grab the collision events BEFORE handleOutgoingChanges() because at this point
- // we have a better idea of which objects we own or should own.
- auto& collisionEvents = _physicsEngine->getCollisionEvents();
-
- getEntities()->getTree()->withWriteLock([&] {
- PerformanceTimer perfTimer("handleOutgoingChanges");
-
- const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
- _entitySimulation->handleChangedMotionStates(outgoingChanges);
- avatarManager->handleChangedMotionStates(outgoingChanges);
-
- const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
- _entitySimulation->handleDeactivatedMotionStates(deactivations);
+ });
+ getEntities()->getTree()->withReadLock([&] {
+ _entitySimulation->getObjectsToChange(motionStates);
+ VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
+ _entitySimulation->setObjectsToChange(stillNeedChange);
});
- if (!_aboutToQuit) {
- // handleCollisionEvents() AFTER handleOutgoinChanges()
- PerformanceTimer perfTimer("entities");
- avatarManager->handleCollisionEvents(collisionEvents);
- // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
- // deadlock.)
- _entitySimulation->handleCollisionEvents(collisionEvents);
+ _entitySimulation->applyDynamicChanges();
- // NOTE: the getEntities()->update() call below will wait for lock
- // and will simulate entity motion (the EntityTree has been given an EntitySimulation).
- getEntities()->update(true); // update the models...
- }
+ avatarManager->getObjectsToRemoveFromPhysics(motionStates);
+ _physicsEngine->removeObjects(motionStates);
+ avatarManager->getObjectsToAddToPhysics(motionStates);
+ _physicsEngine->addObjects(motionStates);
+ avatarManager->getObjectsToChange(motionStates);
+ _physicsEngine->changeObjects(motionStates);
- myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
-
- if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
- Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) {
- _physicsEngine->harvestPerformanceStats();
- }
- // NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework
- _physicsEngine->dumpStatsIfNecessary();
+ myAvatar->prepareForPhysicsSimulation();
+ _physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
+ dynamic->prepareForPhysicsSimulation();
+ });
}
+ {
+ PROFILE_RANGE_EX(simulation_physics, "StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount());
+ PerformanceTimer perfTimer("stepSimulation");
+ getEntities()->getTree()->withWriteLock([&] {
+ _physicsEngine->stepSimulation();
+ });
+ }
+ {
+ PROFILE_RANGE_EX(simulation_physics, "HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
+ PerformanceTimer perfTimer("harvestChanges");
+ if (_physicsEngine->hasOutgoingChanges()) {
+ // grab the collision events BEFORE handleOutgoingChanges() because at this point
+ // we have a better idea of which objects we own or should own.
+ auto& collisionEvents = _physicsEngine->getCollisionEvents();
+
+ getEntities()->getTree()->withWriteLock([&] {
+ PerformanceTimer perfTimer("handleOutgoingChanges");
+
+ const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
+ _entitySimulation->handleChangedMotionStates(outgoingChanges);
+ avatarManager->handleChangedMotionStates(outgoingChanges);
+
+ const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
+ _entitySimulation->handleDeactivatedMotionStates(deactivations);
+ });
+
+ if (!_aboutToQuit) {
+ // handleCollisionEvents() AFTER handleOutgoinChanges()
+ PerformanceTimer perfTimer("entities");
+ avatarManager->handleCollisionEvents(collisionEvents);
+ // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
+ // deadlock.)
+ _entitySimulation->handleCollisionEvents(collisionEvents);
+
+ // NOTE: the getEntities()->update() call below will wait for lock
+ // and will simulate entity motion (the EntityTree has been given an EntitySimulation).
+ getEntities()->update(true); // update the models...
+ }
+
+ myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
+
+ if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
+ Menu::getInstance()->isOptionChecked(MenuOption::ExpandPhysicsSimulationTiming)) {
+ _physicsEngine->harvestPerformanceStats();
+ }
+ // NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework
+ _physicsEngine->dumpStatsIfNecessary();
+ }
+ }
+ } else {
+ // update the rendering without any simulation
+ getEntities()->update(false);
}
- } else {
- // update the rendering without any simulation
- getEntities()->update(false);
}
// AvatarManager update
{
- PerformanceTimer perfTimer("AvatarManager");
- _avatarSimCounter.increment();
-
{
+ PerformanceTimer perfTimer("otherAvatars");
PROFILE_RANGE_EX(simulation, "OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
avatarManager->updateOtherAvatars(deltaTime);
}
- qApp->updateMyAvatarLookAtPosition();
-
{
PROFILE_RANGE_EX(simulation, "MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
+ PerformanceTimer perfTimer("MyAvatar");
+ qApp->updateMyAvatarLookAtPosition();
avatarManager->updateMyAvatar(deltaTime);
}
}
+ PerformanceTimer perfTimer("misc");
+
+ bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
+ PerformanceWarning warn(showWarnings, "Application::update()");
+
+ updateLOD(deltaTime);
+
+ // TODO: break these out into distinct perfTimers when they prove interesting
{
PROFILE_RANGE(app, "RayPickManager");
_rayPickManager.update();
@@ -5387,7 +5387,7 @@ bool Application::isHMDMode() const {
return getActiveDisplayPlugin()->isHmd();
}
-float Application::getTargetFrameRate() const { return getActiveDisplayPlugin()->getTargetFrameRate(); }
+float Application::getTargetRenderFrameRate() const { return getActiveDisplayPlugin()->getTargetFrameRate(); }
QRect Application::getDesirableApplicationGeometry() const {
QRect applicationGeometry = getWindow()->geometry();
@@ -5867,6 +5867,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
scriptEngine->registerGlobalObject("Selection", DependencyManager::get().data());
scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get().data());
+ scriptEngine->registerGlobalObject("Wallet", DependencyManager::get().data());
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 54a8fadd28..478642e2da 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -194,10 +194,9 @@ public:
Overlays& getOverlays() { return _overlays; }
-
- size_t getFrameCount() const { return _frameCount; }
- float getFps() const { return _frameCounter.rate(); }
- float getTargetFrameRate() const; // frames/second
+ size_t getRenderFrameCount() const { return _renderFrameCount; }
+ float getRenderLoopRate() const { return _renderLoopCounter.rate(); }
+ float getTargetRenderFrameRate() const; // frames/second
float getFieldOfView() { return _fieldOfView.get(); }
void setFieldOfView(float fov);
@@ -268,8 +267,7 @@ public:
void updateMyAvatarLookAtPosition();
- float getAvatarSimrate() const { return _avatarSimCounter.rate(); }
- float getAverageSimsPerSecond() const { return _simCounter.rate(); }
+ float getGameLoopRate() const { return _gameLoopCounter.rate(); }
void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f);
void takeSecondaryCameraSnapshot();
@@ -531,12 +529,11 @@ private:
QUndoStack _undoStack;
UndoStackScriptingInterface _undoStackScriptingInterface;
- uint32_t _frameCount { 0 };
+ uint32_t _renderFrameCount { 0 };
// Frame Rate Measurement
- RateCounter<> _frameCounter;
- RateCounter<> _avatarSimCounter;
- RateCounter<> _simCounter;
+ RateCounter<500> _renderLoopCounter;
+ RateCounter<500> _gameLoopCounter;
FrameTimingsScriptingInterface _frameTimingsScriptingInterface;
diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp
index ac9aecf66c..bbcd8266d7 100644
--- a/interface/src/Application_render.cpp
+++ b/interface/src/Application_render.cpp
@@ -33,11 +33,11 @@ void Application::paintGL() {
return;
}
- _frameCount++;
+ _renderFrameCount++;
_lastTimeRendered.start();
auto lastPaintBegin = usecTimestampNow();
- PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_frameCount);
+ PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_renderFrameCount);
PerformanceTimer perfTimer("paintGL");
if (nullptr == _displayPlugin) {
@@ -54,7 +54,7 @@ void Application::paintGL() {
PROFILE_RANGE(render, "/pluginBeginFrameRender");
// If a display plugin loses it's underlying support, it
// needs to be able to signal us to not use it
- if (!displayPlugin->beginFrameRender(_frameCount)) {
+ if (!displayPlugin->beginFrameRender(_renderFrameCount)) {
updateDisplayMode();
return;
}
@@ -105,7 +105,7 @@ void Application::paintGL() {
{
PROFILE_RANGE(render, "/updateCompositor");
- getApplicationCompositor().setFrameInfo(_frameCount, eyeToWorld, sensorToWorld);
+ getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
}
gpu::FramebufferPointer finalFramebuffer;
@@ -141,7 +141,7 @@ void Application::paintGL() {
}
auto frame = _gpuContext->endFrame();
- frame->frameIndex = _frameCount;
+ frame->frameIndex = _renderFrameCount;
frame->framebuffer = finalFramebuffer;
frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) {
DependencyManager::get()->releaseFramebuffer(framebuffer);
@@ -152,7 +152,7 @@ void Application::paintGL() {
{
PROFILE_RANGE(render, "/pluginOutput");
PerformanceTimer perfTimer("pluginOutput");
- _frameCounter.increment();
+ _renderLoopCounter.increment();
displayPlugin->submitFrame(frame);
}
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index a6d77c8d03..5d8393ba7a 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -252,11 +252,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
qApp->getMain3DScene()->enqueueTransaction(transaction);
}
- _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
_numAvatarsUpdated = numAvatarsUpdated;
_numAvatarsNotUpdated = numAVatarsNotUpdated;
simulateAvatarFades(deltaTime);
+
+ _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
}
void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scene) {
diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp
index d8860192ad..a68a6fe929 100644
--- a/interface/src/commerce/Ledger.cpp
+++ b/interface/src/commerce/Ledger.cpp
@@ -164,11 +164,7 @@ void Ledger::historySuccess(QNetworkReply& reply) {
// turns out on my machine, toLocalTime convert to some weird timezone, yet the
// systemTimeZone is correct. To avoid a strange bug with other's systems too, lets
// be explicit
-#ifdef Q_OS_MAC
- QDateTime createdAt = QDateTime::fromTime_t(valueObject["created_at"].toInt(), Qt::UTC);
-#else
QDateTime createdAt = QDateTime::fromSecsSinceEpoch(valueObject["created_at"].toInt(), Qt::UTC);
-#endif
QDateTime localCreatedAt = createdAt.toTimeZone(QTimeZone::systemTimeZone());
valueObject["text"] = QString("%1 sent %2 %3 with message \"%4\"").
arg(from, to, coloredQuantityAndAssetTitle, valueObject["message"].toString());
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 93de2ef566..dbd84594bc 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -15,6 +15,7 @@
#include "Ledger.h"
#include "Wallet.h"
#include
+#include "scripting/WalletScriptingInterface.h"
HIFI_QML_DEF(QmlCommerce)
@@ -28,6 +29,37 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
connect(ledger.data(), &Ledger::historyResult, this, &QmlCommerce::historyResult);
connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult);
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
+ connect(ledger.data(), &Ledger::accountResult, this, [&]() {
+ auto wallet = DependencyManager::get();
+ auto walletScriptingInterface = DependencyManager::get();
+ uint status;
+
+ if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) {
+ status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP;
+ } else if (!wallet->walletIsAuthenticatedWithPassphrase()) {
+ status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED;
+ } else {
+ status = (uint)WalletStatus::WALLET_STATUS_READY;
+ }
+
+ walletScriptingInterface->setWalletStatus(status);
+ emit walletStatusResult(status);
+ });
+}
+
+void QmlCommerce::getWalletStatus() {
+ auto walletScriptingInterface = DependencyManager::get();
+ uint status;
+
+ if (DependencyManager::get()->isLoggedIn()) {
+ // This will set account info for the wallet, allowing us to decrypt and display the security image.
+ account();
+ } else {
+ status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN;
+ emit walletStatusResult(status);
+ walletScriptingInterface->setWalletStatus(status);
+ return;
+ }
}
void QmlCommerce::getLoginStatus() {
@@ -36,7 +68,7 @@ void QmlCommerce::getLoginStatus() {
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get();
- wallet->sendKeyFilePathIfExists();
+ emit keyFilePathIfExistsResult(wallet->getKeyFilePath());
}
void QmlCommerce::getWalletAuthenticatedStatus() {
@@ -85,13 +117,18 @@ void QmlCommerce::history() {
ledger->history(wallet->listPublicKeys());
}
+void QmlCommerce::changePassphrase(const QString& oldPassphrase, const QString& newPassphrase) {
+ auto wallet = DependencyManager::get();
+ if ((wallet->getPassphrase()->isEmpty() || wallet->getPassphrase() == oldPassphrase) && !newPassphrase.isEmpty()) {
+ emit changePassphraseStatusResult(wallet->changePassphrase(newPassphrase));
+ } else {
+ emit changePassphraseStatusResult(false);
+ }
+}
+
void QmlCommerce::setPassphrase(const QString& passphrase) {
auto wallet = DependencyManager::get();
- if(wallet->getPassphrase() && !wallet->getPassphrase()->isEmpty() && !passphrase.isEmpty()) {
- wallet->changePassphrase(passphrase);
- } else {
- wallet->setPassphrase(passphrase);
- }
+ wallet->setPassphrase(passphrase);
getWalletAuthenticatedStatus();
}
diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h
index 42f44a3a85..8e6af6da65 100644
--- a/interface/src/commerce/QmlCommerce.h
+++ b/interface/src/commerce/QmlCommerce.h
@@ -27,11 +27,21 @@ class QmlCommerce : public OffscreenQmlDialog {
public:
QmlCommerce(QQuickItem* parent = nullptr);
+ enum WalletStatus {
+ WALLET_STATUS_NOT_LOGGED_IN = 0,
+ WALLET_STATUS_NOT_SET_UP,
+ WALLET_STATUS_NOT_AUTHENTICATED,
+ WALLET_STATUS_READY
+ };
+
signals:
+ void walletStatusResult(uint walletStatus);
+
void loginStatusResult(bool isLoggedIn);
void keyFilePathIfExistsResult(const QString& path);
void securityImageResult(bool exists);
void walletAuthenticatedStatusResult(bool isAuthenticated);
+ void changePassphraseStatusResult(bool changeSuccess);
void buyResult(QJsonObject result);
// Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
@@ -42,6 +52,8 @@ signals:
void accountResult(QJsonObject result);
protected:
+ Q_INVOKABLE void getWalletStatus();
+
Q_INVOKABLE void getLoginStatus();
Q_INVOKABLE void getKeyFilePathIfExists();
Q_INVOKABLE void getSecurityImage();
@@ -49,6 +61,7 @@ protected:
Q_INVOKABLE void chooseSecurityImage(const QString& imageFile);
Q_INVOKABLE void setPassphrase(const QString& passphrase);
+ Q_INVOKABLE void changePassphrase(const QString& oldPassphrase, const QString& newPassphrase);
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
Q_INVOKABLE void balance();
diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp
index e1e2849a04..cc2039da48 100644
--- a/interface/src/commerce/Wallet.cpp
+++ b/interface/src/commerce/Wallet.cpp
@@ -284,7 +284,7 @@ Wallet::Wallet() {
auto nodeList = DependencyManager::get();
auto& packetReceiver = nodeList->getPacketReceiver();
- packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "verifyOwnerChallenge");
+ packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
}
Wallet::~Wallet() {
@@ -468,7 +468,7 @@ bool Wallet::generateKeyPair() {
// TODO: redo this soon -- need error checking and so on
writeSecurityImage(_securityImage, keyFilePath());
- sendKeyFilePathIfExists();
+ emit keyFilePathIfExistsResult(getKeyFilePath());
QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last();
QString key = keyPair.first->toBase64();
_publicKeys.push_back(key);
@@ -559,14 +559,14 @@ void Wallet::chooseSecurityImage(const QString& filename) {
emit securityImageResult(success);
}
-void Wallet::getSecurityImage() {
+bool Wallet::getSecurityImage() {
unsigned char* data;
int dataLen;
// if already decrypted, don't do it again
if (_securityImage) {
emit securityImageResult(true);
- return;
+ return true;
}
bool success = false;
@@ -585,14 +585,15 @@ void Wallet::getSecurityImage() {
success = true;
}
emit securityImageResult(success);
+ return success;
}
-void Wallet::sendKeyFilePathIfExists() {
+QString Wallet::getKeyFilePath() {
QString filePath(keyFilePath());
QFileInfo fileInfo(filePath);
if (fileInfo.exists()) {
- emit keyFilePathIfExistsResult(filePath);
+ return filePath;
} else {
- emit keyFilePathIfExistsResult("");
+ return "";
}
}
diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h
index 59812d5222..807080e6ea 100644
--- a/interface/src/commerce/Wallet.h
+++ b/interface/src/commerce/Wallet.h
@@ -32,8 +32,8 @@ public:
QStringList listPublicKeys();
QString signWithKey(const QByteArray& text, const QString& key);
void chooseSecurityImage(const QString& imageFile);
- void getSecurityImage();
- void sendKeyFilePathIfExists();
+ bool getSecurityImage();
+ QString getKeyFilePath();
void setSalt(const QByteArray& salt) { _salt = salt; }
QByteArray getSalt() { return _salt; }
diff --git a/interface/src/main.cpp b/interface/src/main.cpp
index cb90160cfe..5c07bebc23 100644
--- a/interface/src/main.cpp
+++ b/interface/src/main.cpp
@@ -49,6 +49,10 @@ int main(int argc, const char* argv[]) {
CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION };
#endif
+#ifdef Q_OS_LINUX
+ QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
+#endif
+
disableQtBearerPoll(); // Fixes wifi ping spikes
QElapsedTimer startupTime;
diff --git a/interface/src/scripting/RatesScriptingInterface.h b/interface/src/scripting/RatesScriptingInterface.h
index 7bcab0ea70..5658ed99a0 100644
--- a/interface/src/scripting/RatesScriptingInterface.h
+++ b/interface/src/scripting/RatesScriptingInterface.h
@@ -22,16 +22,14 @@ class RatesScriptingInterface : public QObject {
Q_PROPERTY(float newFrame READ getNewFrameRate)
Q_PROPERTY(float dropped READ getDropRate)
Q_PROPERTY(float simulation READ getSimulationRate)
- Q_PROPERTY(float avatar READ getAvatarRate)
public:
RatesScriptingInterface(QObject* parent) : QObject(parent) {}
- float getRenderRate() { return qApp->getFps(); }
+ float getRenderRate() { return qApp->getRenderLoopRate(); }
float getPresentRate() { return qApp->getActiveDisplayPlugin()->presentRate(); }
float getNewFrameRate() { return qApp->getActiveDisplayPlugin()->newFramePresentRate(); }
float getDropRate() { return qApp->getActiveDisplayPlugin()->droppedFrameRate(); }
- float getSimulationRate() { return qApp->getAverageSimsPerSecond(); }
- float getAvatarRate() { return qApp->getAvatarSimrate(); }
+ float getSimulationRate() { return qApp->getGameLoopRate(); }
};
#endif // HIFI_INTERFACE_RATES_SCRIPTING_INTERFACE_H
diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp
new file mode 100644
index 0000000000..555e9477b0
--- /dev/null
+++ b/interface/src/scripting/WalletScriptingInterface.cpp
@@ -0,0 +1,47 @@
+//
+// WalletScriptingInterface.cpp
+// interface/src/scripting
+//
+// Created by Zach Fox on 2017-09-29.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "WalletScriptingInterface.h"
+
+CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) {
+ Q_ASSERT(QThread::currentThread() == qApp->thread());
+}
+
+WalletScriptingInterface::WalletScriptingInterface() {
+}
+
+static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml";
+void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href));
+ return;
+ }
+
+ auto tabletScriptingInterface = DependencyManager::get();
+ auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+
+ tablet->loadQMLSource(CHECKOUT_QML_PATH);
+ DependencyManager::get()->openTablet();
+
+ QQuickItem* root = nullptr;
+ if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) {
+ root = DependencyManager::get()->getRootItem();
+ } else {
+ root = tablet->getTabletRoot();
+ }
+ CheckoutProxy* checkout = new CheckoutProxy(root->findChild("checkout"));
+
+ // Example: Wallet.buy("Test Flaregun", "0d90d21c-ce7a-4990-ad18-e9d2cf991027", 17, "http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json");
+ checkout->writeProperty("itemName", name);
+ checkout->writeProperty("itemId", id);
+ checkout->writeProperty("itemPrice", price);
+ checkout->writeProperty("itemHref", href);
+}
\ No newline at end of file
diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h
new file mode 100644
index 0000000000..31b42094cf
--- /dev/null
+++ b/interface/src/scripting/WalletScriptingInterface.h
@@ -0,0 +1,51 @@
+
+// WalletScriptingInterface.h
+// interface/src/scripting
+//
+// Created by Zach Fox on 2017-09-29.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_WalletScriptingInterface_h
+#define hifi_WalletScriptingInterface_h
+
+#include
+#include
+
+#include "scripting/HMDScriptingInterface.h"
+#include
+#include
+#include
+#include "Application.h"
+
+class CheckoutProxy : public QmlWrapper {
+ Q_OBJECT
+public:
+ CheckoutProxy(QObject* qmlObject, QObject* parent = nullptr);
+};
+
+
+class WalletScriptingInterface : public QObject, public Dependency {
+ Q_OBJECT
+
+ Q_PROPERTY(uint walletStatus READ getWalletStatus WRITE setWalletStatus NOTIFY walletStatusChanged)
+
+public:
+ WalletScriptingInterface();
+
+ Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
+ void setWalletStatus(const uint& status) { _walletStatus = status; }
+
+ Q_INVOKABLE void buy(const QString& name, const QString& id, const int& price, const QString& href);
+
+signals:
+ void walletStatusChanged();
+
+private:
+ uint _walletStatus;
+};
+
+#endif // hifi_WalletScriptingInterface_h
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index 4b981207f1..c99e190d12 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -171,6 +171,11 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc
Setting::Handle(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location);
}
+bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
+ auto offscreenUi = DependencyManager::get();
+ return offscreenUi->isPointOnDesktopWindow(point);
+}
+
/// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and
/// might be in same thread as a script that sets the reticle to invisible
void WindowScriptingInterface::ensureReticleVisible() const {
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index 0d58e6162d..61aaec7bea 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -72,6 +72,7 @@ public slots:
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
bool isPhysicsEnabled();
bool setDisplayTexture(const QString& name);
+ bool isPointOnDesktopWindow(QVariant point);
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton);
diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp
index 767e499503..8140dfb8ae 100644
--- a/interface/src/ui/Stats.cpp
+++ b/interface/src/ui/Stats.cpp
@@ -8,6 +8,7 @@
#include "Stats.h"
+#include
#include
#include
@@ -116,12 +117,6 @@ void Stats::updateStats(bool force) {
}
}
- bool shouldDisplayTimingDetail = Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) &&
- Menu::getInstance()->isOptionChecked(MenuOption::Stats) && isExpanded();
- if (shouldDisplayTimingDetail != PerformanceTimer::isActive()) {
- PerformanceTimer::setActive(shouldDisplayTimingDetail);
- }
-
auto nodeList = DependencyManager::get();
auto avatarManager = DependencyManager::get();
// we need to take one avatar out so we don't include ourselves
@@ -129,7 +124,7 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(updatedAvatarCount, avatarManager->getNumAvatarsUpdated());
STAT_UPDATE(notUpdatedAvatarCount, avatarManager->getNumAvatarsNotUpdated());
STAT_UPDATE(serverCount, (int)nodeList->size());
- STAT_UPDATE_FLOAT(framerate, qApp->getFps(), 0.1f);
+ STAT_UPDATE_FLOAT(renderrate, qApp->getRenderLoopRate(), 0.1f);
if (qApp->getActiveDisplayPlugin()) {
auto displayPlugin = qApp->getActiveDisplayPlugin();
auto stats = displayPlugin->getHardwareStats();
@@ -137,7 +132,6 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(longrenders, stats["long_render_count"].toInt());
STAT_UPDATE(longsubmits, stats["long_submit_count"].toInt());
STAT_UPDATE(longframes, stats["long_frame_count"].toInt());
- STAT_UPDATE_FLOAT(renderrate, displayPlugin->renderRate(), 0.1f);
STAT_UPDATE_FLOAT(presentrate, displayPlugin->presentRate(), 0.1f);
STAT_UPDATE_FLOAT(presentnewrate, displayPlugin->newFramePresentRate(), 0.1f);
STAT_UPDATE_FLOAT(presentdroprate, displayPlugin->droppedFrameRate(), 0.1f);
@@ -150,8 +144,7 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(presentnewrate, -1);
STAT_UPDATE(presentdroprate, -1);
}
- STAT_UPDATE(simrate, (int)qApp->getAverageSimsPerSecond());
- STAT_UPDATE(avatarSimrate, (int)qApp->getAvatarSimrate());
+ STAT_UPDATE(gameLoopRate, (int)qApp->getGameLoopRate());
auto bandwidthRecorder = DependencyManager::get();
STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());
@@ -406,14 +399,21 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get()->getLODFeedbackText());
}
- bool performanceTimerIsActive = PerformanceTimer::isActive();
- bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails);
- if (displayPerf && performanceTimerIsActive) {
- if (!_timingExpanded) {
- _timingExpanded = true;
+
+ bool performanceTimerShouldBeActive = Menu::getInstance()->isOptionChecked(MenuOption::Stats) && _expanded;
+ if (performanceTimerShouldBeActive != PerformanceTimer::isActive()) {
+ PerformanceTimer::setActive(performanceTimerShouldBeActive);
+ }
+ if (performanceTimerShouldBeActive) {
+ PerformanceTimer::tallyAllTimerRecords(); // do this even if we're not displaying them, so they don't stack up
+ }
+
+ if (performanceTimerShouldBeActive &&
+ Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails)) {
+ if (!_showTimingDetails) {
+ _showTimingDetails = true;
emit timingExpandedChanged();
}
- PerformanceTimer::tallyAllTimerRecords(); // do this even if we're not displaying them, so they don't stack up
// we will also include room for 1 line per timing record and a header of 4 lines
// Timing details...
@@ -453,10 +453,55 @@ void Stats::updateStats(bool force) {
}
_timingStats = perfLines;
emit timingStatsChanged();
- } else if (_timingExpanded) {
- _timingExpanded = false;
+ } else if (_showTimingDetails) {
+ _showTimingDetails = false;
emit timingExpandedChanged();
}
+
+ if (_expanded && performanceTimerShouldBeActive) {
+ if (!_showGameUpdateStats) {
+ _showGameUpdateStats = true;
+ }
+ class SortableStat {
+ public:
+ SortableStat(QString a, float p) : message(a), priority(p) {}
+ QString message;
+ float priority;
+ bool operator<(const SortableStat& other) const { return priority < other.priority; }
+ };
+
+ const QMap& allRecords = PerformanceTimer::getAllTimerRecords();
+ std::priority_queue idleUpdateStats;
+ auto itr = allRecords.find("/idle/update");
+ if (itr != allRecords.end()) {
+ float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
+ _gameUpdateStats = QString("/idle/update = %1 ms").arg(dt);
+
+ QVector categories = { "devices", "physics", "otherAvatars", "MyAvatar", "misc" };
+ for (int32_t j = 0; j < categories.size(); ++j) {
+ QString recordKey = "/idle/update/" + categories[j];
+ itr = allRecords.find(recordKey);
+ if (itr != allRecords.end()) {
+ float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC;
+ QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt);
+ idleUpdateStats.push(SortableStat(message, dt));
+ }
+ }
+ while (!idleUpdateStats.empty()) {
+ SortableStat stat = idleUpdateStats.top();
+ _gameUpdateStats += stat.message;
+ idleUpdateStats.pop();
+ }
+ emit gameUpdateStatsChanged();
+ } else if (_gameUpdateStats != "") {
+ _gameUpdateStats = "";
+ emit gameUpdateStatsChanged();
+ }
+ } else if (_showGameUpdateStats) {
+ _showGameUpdateStats = false;
+ _gameUpdateStats = "";
+ emit gameUpdateStatsChanged();
+ }
}
void Stats::setRenderDetails(const render::RenderDetails& details) {
diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h
index b3c920d4ef..af3189f20b 100644
--- a/interface/src/ui/Stats.h
+++ b/interface/src/ui/Stats.h
@@ -32,8 +32,6 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, serverCount, 0)
// How often the app is creating new gpu::Frames
- STATS_PROPERTY(float, framerate, 0)
- // How often the display plugin is executing a given frame
STATS_PROPERTY(float, renderrate, 0)
// How often the display plugin is presenting to the device
STATS_PROPERTY(float, presentrate, 0)
@@ -47,8 +45,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(float, presentnewrate, 0)
STATS_PROPERTY(float, presentdroprate, 0)
- STATS_PROPERTY(int, simrate, 0)
- STATS_PROPERTY(int, avatarSimrate, 0)
+ STATS_PROPERTY(int, gameLoopRate, 0)
STATS_PROPERTY(int, avatarCount, 0)
STATS_PROPERTY(int, updatedAvatarCount, 0)
STATS_PROPERTY(int, notUpdatedAvatarCount, 0)
@@ -108,6 +105,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(QString, packetStats, QString())
STATS_PROPERTY(QString, lodStatus, QString())
STATS_PROPERTY(QString, timingStats, QString())
+ STATS_PROPERTY(QString, gameUpdateStats, QString())
STATS_PROPERTY(int, serverElements, 0)
STATS_PROPERTY(int, serverInternal, 0)
STATS_PROPERTY(int, serverLeaves, 0)
@@ -148,7 +146,7 @@ public:
void updateStats(bool force = false);
bool isExpanded() { return _expanded; }
- bool isTimingExpanded() { return _timingExpanded; }
+ bool isTimingExpanded() { return _showTimingDetails; }
void setExpanded(bool expanded) {
if (_expanded != expanded) {
@@ -167,7 +165,6 @@ signals:
void longrendersChanged();
void longframesChanged();
void appdroppedChanged();
- void framerateChanged();
void expandedChanged();
void timingExpandedChanged();
void serverCountChanged();
@@ -176,8 +173,7 @@ signals:
void presentnewrateChanged();
void presentdroprateChanged();
void stutterrateChanged();
- void simrateChanged();
- void avatarSimrateChanged();
+ void gameLoopRateChanged();
void avatarCountChanged();
void updatedAvatarCountChanged();
void notUpdatedAvatarCountChanged();
@@ -242,6 +238,7 @@ signals:
void localInternalChanged();
void localLeavesChanged();
void timingStatsChanged();
+ void gameUpdateStatsChanged();
void glContextSwapchainMemoryChanged();
void qmlTextureMemoryChanged();
void texturePendingTransfersChanged();
@@ -267,7 +264,8 @@ private:
int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process
bool _resetRecentMaxPacketsSoon{ true };
bool _expanded{ false };
- bool _timingExpanded{ false };
+ bool _showTimingDetails{ false };
+ bool _showGameUpdateStats{ false };
QString _monospaceFont;
const AudioIOStats* _audioStats;
QStringList _downloadUrls = QStringList();
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp
index 8e4254a786..39fd4f9377 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.cpp
+++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp
@@ -201,7 +201,8 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt
void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) {
qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID;
- if (_commerceSettingSwitch.get()) {
+ Setting::Handle _settingSwitch{ "commerce", false };
+ if (_settingSwitch.get()) {
openInspectionCertificate();
} else {
openMarketplace();
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h
index ec5913444f..b4d3ddc0c2 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.h
+++ b/interface/src/ui/overlays/ContextOverlayInterface.h
@@ -79,8 +79,6 @@ private:
bool _isInMarketplaceInspectionMode { false };
- Setting::Handle _commerceSettingSwitch{ "commerce", false };
-
void openInspectionCertificate();
void openMarketplace();
void enableEntityHighlight(const EntityItemID& entityItemID);
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
index 2080fa5ea6..51f4a33bbd 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h
@@ -129,10 +129,10 @@ protected:
bool _vsyncEnabled { true };
QThread* _presentThread{ nullptr };
std::queue _newFrameQueue;
- RateCounter<100> _droppedFrameRate;
- RateCounter<100> _newFrameRate;
- RateCounter<100> _presentRate;
- RateCounter<100> _renderRate;
+ RateCounter<200> _droppedFrameRate;
+ RateCounter<200> _newFrameRate;
+ RateCounter<200> _presentRate;
+ RateCounter<200> _renderRate;
gpu::FramePointer _currentFrame;
gpu::Frame* _lastFrame { nullptr };
diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt
index 19341ec3e2..322d69da16 100644
--- a/libraries/entities/CMakeLists.txt
+++ b/libraries/entities/CMakeLists.txt
@@ -1,3 +1,4 @@
set(TARGET_NAME entities)
setup_hifi_library(Network Script)
+include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
link_hifi_libraries(shared networking octree avatars)
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 6e2e52b380..a1b7ff54de 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -13,6 +13,10 @@
#include
#include
+#include
+#include // see comments for DEBUG_CERT
+#include
+#include
#include
@@ -34,6 +38,7 @@
Q_DECLARE_METATYPE(EntityItemPointer);
int entityItemPointernMetaTypeId = qRegisterMetaType();
+
int EntityItem::_maxActionsDataSize = 800;
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
@@ -95,7 +100,19 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_DYNAMIC;
requestedProperties += PROP_LOCKED;
requestedProperties += PROP_USER_DATA;
+
+ // Certifiable properties
+ requestedProperties += PROP_ITEM_NAME;
+ requestedProperties += PROP_ITEM_DESCRIPTION;
+ requestedProperties += PROP_ITEM_CATEGORIES;
+ requestedProperties += PROP_ITEM_ARTIST;
+ requestedProperties += PROP_ITEM_LICENSE;
+ requestedProperties += PROP_LIMITED_RUN;
requestedProperties += PROP_MARKETPLACE_ID;
+ requestedProperties += PROP_EDITION_NUMBER;
+ requestedProperties += PROP_ENTITY_INSTANCE_NUMBER;
+ requestedProperties += PROP_CERTIFICATE_ID;
+
requestedProperties += PROP_NAME;
requestedProperties += PROP_HREF;
requestedProperties += PROP_DESCRIPTION;
@@ -240,7 +257,19 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic());
APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData());
+
+ // Certifiable Properties
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, getItemName());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, getItemDescription());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, getItemCategories());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, getItemArtist());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, getItemLicense());
+ APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, getLimitedRun());
+ APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber());
+ APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber());
+ APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID());
+
APPEND_ENTITY_PROPERTY(PROP_NAME, getName());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
@@ -791,6 +820,17 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) {
READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
}
+ if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_CERTIFICATE_PROPERTIES) {
+ READ_ENTITY_PROPERTY(PROP_ITEM_NAME, QString, setItemName);
+ READ_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, QString, setItemDescription);
+ READ_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, QString, setItemCategories);
+ READ_ENTITY_PROPERTY(PROP_ITEM_ARTIST, QString, setItemArtist);
+ READ_ENTITY_PROPERTY(PROP_ITEM_LICENSE, QString, setItemLicense);
+ READ_ENTITY_PROPERTY(PROP_LIMITED_RUN, quint32, setLimitedRun);
+ READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber);
+ READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber);
+ READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID);
+ }
READ_ENTITY_PROPERTY(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
@@ -1208,7 +1248,19 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dynamic, getDynamic);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
+
+ // Certifiable Properties
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemName, getItemName);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemDescription, getItemDescription);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemCategories, getItemCategories);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemArtist, getItemArtist);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemLicense, getItemLicense);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(limitedRun, getLimitedRun);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(editionNumber, getEditionNumber);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityInstanceNumber, getEntityInstanceNumber);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID);
+
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
@@ -1303,7 +1355,19 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
+
+ // Certifiable Properties
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemName, setItemName);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemDescription, setItemDescription);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemCategories, setItemCategories);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemArtist, setItemArtist);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemLicense, setItemLicense);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(limitedRun, setLimitedRun);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(editionNumber, setEditionNumber);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityInstanceNumber, setEntityInstanceNumber);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID);
+
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
@@ -1510,6 +1574,107 @@ float EntityItem::getRadius() const {
return 0.5f * glm::length(getDimensions());
}
+// Checking Certifiable Properties
+#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N()
+#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString()
+#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N())
+QByteArray EntityItem::getStaticCertificateJSON() const {
+ // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
+ // The static certificate properties include all an only those properties that cannot be changed without altering the identity
+ // of the entity as reviewed during the certification submission.
+
+ QJsonObject json;
+ EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"!
+ // It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically
+ // to help maintainence in two different code bases.
+ if (!propertySet.getAnimation().getURL().isEmpty()) {
+ json["animation.url"] = propertySet.getAnimation().getURL();
+ }
+ ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
+ ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
+ ADD_INT_PROPERTY(editionNumber, EditionNumber);
+ ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber);
+ ADD_STRING_PROPERTY(itemArtist, ItemArtist);
+ ADD_STRING_PROPERTY(itemCategories, ItemCategories);
+ ADD_STRING_PROPERTY(itemDescription, ItemDescription);
+ ADD_STRING_PROPERTY(itemLicense, ItemLicense);
+ ADD_STRING_PROPERTY(itemName, ItemName);
+ ADD_INT_PROPERTY(limitedRun, LimitedRun);
+ ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
+ ADD_STRING_PROPERTY(modelURL, ModelURL);
+ ADD_STRING_PROPERTY(script, Script);
+ ADD_ENUM_PROPERTY(shapeType, ShapeType);
+ json["type"] = EntityTypes::getEntityTypeName(propertySet.getType());
+
+ return QJsonDocument(json).toJson(QJsonDocument::Compact);
+}
+QByteArray EntityItem::getStaticCertificateHash() const {
+ return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
+}
+
+#ifdef DEBUG_CERT
+QString EntityItem::computeCertificateID() {
+ // Until the marketplace generates it, compute and answer the certificateID here.
+ // Does not set it, as that will have to be done from script engine in order to update server, etc.
+ const auto hash = getStaticCertificateHash();
+ const auto text = reinterpret_cast(hash.constData());
+ const unsigned int textLength = hash.length();
+
+ const char privateKey[] = "-----BEGIN RSA PRIVATE KEY-----\n\
+MIIBOQIBAAJBALCoBiDAZOClO26tC5pd7JikBL61WIgpAqbcNnrV/TcG6LPI7Zbi\n\
+MjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQJABvOlwhYwIhL+gr12jm2R\n\
+yPPzZ9nVEQ6kFxLlZfIT09119fd6OU1X5d4sHWfMfSIEgjwQIDS3ZU1kY3XKo87X\n\
+zQIhAOPHlYa1OC7BLhaTouy68qIU2vCKLP8mt4S31/TT0UOnAiEAxor6gU6yupTQ\n\
+yuyV3yHvr5LkZKBGqhjmOTmDfgtX7ncCIChGbgX3nQuHVOLhD/nTxHssPNozVGl5\n\
+KxHof+LmYSYZAiB4U+yEh9SsXdq40W/3fpLMPuNq1PRezJ5jGidGMcvF+wIgUNec\n\
+3Kg2U+CVZr8/bDT/vXRrsKj1zfobYuvbfVH02QY=\n\
+-----END RSA PRIVATE KEY-----";
+ BIO* bio = BIO_new_mem_buf((void*)privateKey, sizeof(privateKey));
+ RSA* rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
+
+ QByteArray signature(RSA_size(rsa), 0);
+ unsigned int signatureLength = 0;
+ const int signOK = RSA_sign(NID_sha256, text, textLength, reinterpret_cast(signature.data()), &signatureLength, rsa);
+ BIO_free(bio);
+ RSA_free(rsa);
+ if (!signOK) {
+ qCWarning(entities) << "Unable to compute signature for" << getName() << getEntityItemID();
+ return "";
+ }
+ return signature.toBase64();
+#endif
+}
+
+bool EntityItem::verifyStaticCertificateProperties() {
+ // True IIF a non-empty certificateID matches the static certificate json.
+ // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
+
+ if (getCertificateID().isEmpty()) {
+ return false;
+ }
+ const auto signatureBytes = QByteArray::fromBase64(getCertificateID().toLatin1());
+ const auto signature = reinterpret_cast(signatureBytes.constData());
+ const unsigned int signatureLength = signatureBytes.length();
+
+ const auto hash = getStaticCertificateHash();
+ const auto text = reinterpret_cast(hash.constData());
+ const unsigned int textLength = hash.length();
+
+ // After DEBUG_CERT ends, we will get/cache this once from the marketplace when needed, and it likely won't be RSA.
+ const char publicKey[] = "-----BEGIN PUBLIC KEY-----\n\
+MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALCoBiDAZOClO26tC5pd7JikBL61WIgp\n\
+AqbcNnrV/TcG6LPI7ZbiMjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQ==\n\
+-----END PUBLIC KEY-----";
+ BIO *bio = BIO_new_mem_buf((void*)publicKey, sizeof(publicKey));
+ EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
+ RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
+ bool answer = RSA_verify(NID_sha256, text, textLength, signature, signatureLength, rsa);
+ BIO_free(bio);
+ RSA_free(rsa);
+ EVP_PKEY_free(evp_key);
+ return answer;
+}
+
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
glm::mat4 scale = glm::scale(getDimensions());
@@ -2762,19 +2927,33 @@ void EntityItem::setUserData(const QString& value) {
});
}
-QString EntityItem::getMarketplaceID() const {
- QString result;
- withReadLock([&] {
- result = _marketplaceID;
- });
- return result;
+// Certifiable Properties
+#define DEFINE_PROPERTY_GETTER(type, accessor, var) \
+type EntityItem::get##accessor() const { \
+ type result; \
+ withReadLock([&] { \
+ result = _##var; \
+ }); \
+ return result; \
}
-void EntityItem::setMarketplaceID(const QString& value) {
- withWriteLock([&] {
- _marketplaceID = value;
- });
+#define DEFINE_PROPERTY_SETTER(type, accessor, var) \
+void EntityItem::set##accessor(const type & value) { \
+ withWriteLock([&] { \
+ _##var = value; \
+ }); \
}
+#define DEFINE_PROPERTY_ACCESSOR(type, accessor, var) DEFINE_PROPERTY_GETTER(type, accessor, var) DEFINE_PROPERTY_SETTER(type, accessor, var)
+DEFINE_PROPERTY_ACCESSOR(QString, ItemName, itemName)
+DEFINE_PROPERTY_ACCESSOR(QString, ItemDescription, itemDescription)
+DEFINE_PROPERTY_ACCESSOR(QString, ItemCategories, itemCategories)
+DEFINE_PROPERTY_ACCESSOR(QString, ItemArtist, itemArtist)
+DEFINE_PROPERTY_ACCESSOR(QString, ItemLicense, itemLicense)
+DEFINE_PROPERTY_ACCESSOR(quint32, LimitedRun, limitedRun)
+DEFINE_PROPERTY_ACCESSOR(QString, MarketplaceID, marketplaceID)
+DEFINE_PROPERTY_ACCESSOR(quint32, EditionNumber, editionNumber)
+DEFINE_PROPERTY_ACCESSOR(quint32, EntityInstanceNumber, entityInstanceNumber)
+DEFINE_PROPERTY_ACCESSOR(QString, CertificateID, certificateID)
uint32_t EntityItem::getDirtyFlags() const {
uint32_t result;
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index 88750da463..c26f1694a9 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -36,6 +36,9 @@
#include "SimulationFlags.h"
#include "EntityDynamicInterface.h"
+// FIXME: The server-side marketplace will soon create the certificateID. At that point, all of the DEBUG_CERT stuff will go away.
+#define DEBUG_CERT 1
+
class EntitySimulation;
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
@@ -304,8 +307,33 @@ public:
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
void rememberHasSimulationOwnershipBid() const;
+ // Certifiable Properties
+ QString getItemName() const;
+ void setItemName(const QString& value);
+ QString getItemDescription() const;
+ void setItemDescription(const QString& value);
+ QString getItemCategories() const;
+ void setItemCategories(const QString& value);
+ QString getItemArtist() const;
+ void setItemArtist(const QString& value);
+ QString getItemLicense() const;
+ void setItemLicense(const QString& value);
+ quint32 getLimitedRun() const;
+ void setLimitedRun(const quint32&);
QString getMarketplaceID() const;
void setMarketplaceID(const QString& value);
+ quint32 getEditionNumber() const;
+ void setEditionNumber(const quint32&);
+ quint32 getEntityInstanceNumber() const;
+ void setEntityInstanceNumber(const quint32&);
+ QString getCertificateID() const;
+ void setCertificateID(const QString& value);
+ QByteArray getStaticCertificateJSON() const;
+ QByteArray getStaticCertificateHash() const;
+ bool verifyStaticCertificateProperties();
+#ifdef DEBUG_CERT
+ QString computeCertificateID();
+#endif
// TODO: get rid of users of getRadius()...
float getRadius() const;
@@ -524,12 +552,24 @@ protected:
bool _locked { ENTITY_ITEM_DEFAULT_LOCKED };
QString _userData { ENTITY_ITEM_DEFAULT_USER_DATA };
SimulationOwner _simulationOwner;
- QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID };
bool _shouldHighlight { false };
QString _name { ENTITY_ITEM_DEFAULT_NAME };
QString _href; //Hyperlink href
QString _description; //Hyperlink description
+ // Certifiable Properties
+ QString _itemName { ENTITY_ITEM_DEFAULT_ITEM_NAME };
+ QString _itemDescription { ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION };
+ QString _itemCategories { ENTITY_ITEM_DEFAULT_ITEM_CATEGORIES };
+ QString _itemArtist { ENTITY_ITEM_DEFAULT_ITEM_ARTIST };
+ QString _itemLicense { ENTITY_ITEM_DEFAULT_ITEM_LICENSE };
+ quint32 _limitedRun { ENTITY_ITEM_DEFAULT_LIMITED_RUN };
+ QString _certificateID { ENTITY_ITEM_DEFAULT_CERTIFICATE_ID };
+ quint32 _editionNumber { ENTITY_ITEM_DEFAULT_EDITION_NUMBER };
+ quint32 _entityInstanceNumber { ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER };
+ QString _marketplaceID { ENTITY_ITEM_DEFAULT_MARKETPLACE_ID };
+
+
// NOTE: Damping is applied like this: v *= pow(1 - damping, dt)
//
// Hence the damping coefficient must range from 0 (no damping) to 1 (immediate stop).
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index d6de4ec614..007c5dcc6c 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -288,7 +288,19 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_RADIUS_SPREAD, radiusSpread);
CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart);
CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish);
+
+ // Certifiable Properties
+ CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName);
+ CHECK_PROPERTY_CHANGE(PROP_ITEM_DESCRIPTION, itemDescription);
+ CHECK_PROPERTY_CHANGE(PROP_ITEM_CATEGORIES, itemCategories);
+ CHECK_PROPERTY_CHANGE(PROP_ITEM_ARTIST, itemArtist);
+ CHECK_PROPERTY_CHANGE(PROP_ITEM_LICENSE, itemLicense);
+ CHECK_PROPERTY_CHANGE(PROP_LIMITED_RUN, limitedRun);
CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID);
+ CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber);
+ CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber);
+ CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID);
+
CHECK_PROPERTY_CHANGE(PROP_NAME, name);
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode);
CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl);
@@ -405,7 +417,19 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData);
+
+ // Certifiable Properties
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_NAME, itemName);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_DESCRIPTION, itemDescription);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_CATEGORIES, itemCategories);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_ARTIST, itemArtist);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_LICENSE, itemLicense);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIMITED_RUN, limitedRun);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MARKETPLACE_ID, marketplaceID);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EDITION_NUMBER, editionNumber);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID);
+
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL);
@@ -671,7 +695,19 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusSpread, float, setRadiusSpread);
COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart);
COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish);
+
+ // Certifiable Properties
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(itemDescription, QString, setItemDescription);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(itemCategories, QString, setItemCategories);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(itemArtist, QString, setItemArtist);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(itemLicense, QString, setItemLicense);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(limitedRun, quint32, setLimitedRun);
COPY_PROPERTY_FROM_QSCRIPTVALUE(marketplaceID, QString, setMarketplaceID);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID);
+
COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName);
COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL);
@@ -809,7 +845,19 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(radiusSpread);
COPY_PROPERTY_IF_CHANGED(radiusStart);
COPY_PROPERTY_IF_CHANGED(radiusFinish);
+
+ // Certifiable Properties
+ COPY_PROPERTY_IF_CHANGED(itemName);
+ COPY_PROPERTY_IF_CHANGED(itemDescription);
+ COPY_PROPERTY_IF_CHANGED(itemCategories);
+ COPY_PROPERTY_IF_CHANGED(itemArtist);
+ COPY_PROPERTY_IF_CHANGED(itemLicense);
+ COPY_PROPERTY_IF_CHANGED(limitedRun);
COPY_PROPERTY_IF_CHANGED(marketplaceID);
+ COPY_PROPERTY_IF_CHANGED(editionNumber);
+ COPY_PROPERTY_IF_CHANGED(entityInstanceNumber);
+ COPY_PROPERTY_IF_CHANGED(certificateID);
+
COPY_PROPERTY_IF_CHANGED(name);
COPY_PROPERTY_IF_CHANGED(collisionSoundURL);
@@ -981,7 +1029,19 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float);
ADD_PROPERTY_TO_MAP(PROP_RADIUS_START, RadiusStart, radiusStart, float);
ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float);
+
+ // Certifiable Properties
+ ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString);
+ ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString);
+ ADD_PROPERTY_TO_MAP(PROP_ITEM_CATEGORIES, ItemCategories, itemCategories, QString);
+ ADD_PROPERTY_TO_MAP(PROP_ITEM_ARTIST, ItemArtist, itemArtist, QString);
+ ADD_PROPERTY_TO_MAP(PROP_ITEM_LICENSE, ItemLicense, itemLicense, QString);
+ ADD_PROPERTY_TO_MAP(PROP_LIMITED_RUN, LimitedRun, limitedRun, quint32);
ADD_PROPERTY_TO_MAP(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString);
+ ADD_PROPERTY_TO_MAP(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32);
+ ADD_PROPERTY_TO_MAP(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32);
+ ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString);
+
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor);
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float);
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float);
@@ -1334,11 +1394,22 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
properties.getType() == EntityTypes::Sphere) {
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
}
- APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID());
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha());
+
+ // Certifiable Properties
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, properties.getItemName());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, properties.getItemDescription());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, properties.getItemCategories());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, properties.getItemArtist());
+ APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, properties.getItemLicense());
+ APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, properties.getLimitedRun());
+ APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID());
+ APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber());
+ APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber());
+ APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID());
}
if (propertyCount > 0) {
@@ -1632,12 +1703,23 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape);
}
- READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha);
+ // Certifiable Properties
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_NAME, QString, setItemName);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_DESCRIPTION, QString, setItemDescription);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_CATEGORIES, QString, setItemCategories);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_ARTIST, QString, setItemArtist);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_LICENSE, QString, setItemLicense);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIMITED_RUN, quint32, setLimitedRun);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID);
+
return valid;
}
@@ -1746,7 +1828,17 @@ void EntityItemProperties::markAllChanged() {
//_alphaStartChanged = true;
//_alphaFinishChanged = true;
+ // Certifiable Properties
+ _itemNameChanged = true;
+ _itemDescriptionChanged = true;
+ _itemCategoriesChanged = true;
+ _itemArtistChanged = true;
+ _itemLicenseChanged = true;
+ _limitedRunChanged = true;
_marketplaceIDChanged = true;
+ _editionNumberChanged = true;
+ _entityInstanceNumberChanged = true;
+ _certificateIDChanged = true;
_keyLight.markAllChanged();
@@ -2053,9 +2145,39 @@ QList EntityItemProperties::listChangedProperties() {
if (radiusFinishChanged()) {
out += "radiusFinish";
}
+
+ // Certifiable Properties
+ if (itemNameChanged()) {
+ out += "itemName";
+ }
+ if (itemDescriptionChanged()) {
+ out += "itemDescription";
+ }
+ if (itemCategoriesChanged()) {
+ out += "itemCategories";
+ }
+ if (itemArtistChanged()) {
+ out += "itemArtist";
+ }
+ if (itemLicenseChanged()) {
+ out += "itemLicense";
+ }
+ if (limitedRunChanged()) {
+ out += "limitedRun";
+ }
if (marketplaceIDChanged()) {
out += "marketplaceID";
}
+ if (editionNumberChanged()) {
+ out += "editionNumber";
+ }
+ if (entityInstanceNumberChanged()) {
+ out += "entityInstanceNumber";
+ }
+ if (certificateIDChanged()) {
+ out += "certificateID";
+ }
+
if (backgroundModeChanged()) {
out += "backgroundMode";
}
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index dd8ce952d3..1f5b4a4660 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -170,7 +170,6 @@ public:
DEFINE_PROPERTY(PROP_RADIUS_START, RadiusStart, radiusStart, float, particle::DEFAULT_RADIUS_START);
DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, particle::DEFAULT_RADIUS_FINISH);
DEFINE_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool, particle::DEFAULT_EMITTER_SHOULD_TRAIL);
- DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID);
DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup);
DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA);
@@ -203,6 +202,18 @@ public:
DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube());
DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere");
+ // Certifiable Properties - related to Proof of Purchase certificates
+ DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME);
+ DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION);
+ DEFINE_PROPERTY_REF(PROP_ITEM_CATEGORIES, ItemCategories, itemCategories, QString, ENTITY_ITEM_DEFAULT_ITEM_CATEGORIES);
+ DEFINE_PROPERTY_REF(PROP_ITEM_ARTIST, ItemArtist, itemArtist, QString, ENTITY_ITEM_DEFAULT_ITEM_ARTIST);
+ DEFINE_PROPERTY_REF(PROP_ITEM_LICENSE, ItemLicense, itemLicense, QString, ENTITY_ITEM_DEFAULT_ITEM_LICENSE);
+ DEFINE_PROPERTY_REF(PROP_LIMITED_RUN, LimitedRun, limitedRun, quint32, ENTITY_ITEM_DEFAULT_LIMITED_RUN);
+ DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID);
+ DEFINE_PROPERTY_REF(PROP_EDITION_NUMBER, EditionNumber, editionNumber, quint32, ENTITY_ITEM_DEFAULT_EDITION_NUMBER);
+ DEFINE_PROPERTY_REF(PROP_ENTITY_INSTANCE_NUMBER, EntityInstanceNumber, entityInstanceNumber, quint32, ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER);
+ DEFINE_PROPERTY_REF(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString, ENTITY_ITEM_DEFAULT_CERTIFICATE_ID);
+
// these are used when bouncing location data into and out of scripts
DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3);
DEFINE_PROPERTY_REF(PROP_LOCAL_ROTATION, LocalRotation, localRotation, glmQuat, ENTITY_ITEM_DEFAULT_ROTATION);
@@ -426,7 +437,19 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, RadiusSpread, radiusSpread, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, RadiusStart, radiusStart, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, RadiusFinish, radiusFinish, "");
+
+ // Certifiable Properties
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, ItemName, itemName, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, ItemDescription, itemDescription, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, ItemCategories, itemCategories, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, ItemArtist, itemArtist, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, ItemLicense, itemLicense, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, LimitedRun, limitedRun, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, EditionNumber, editionNumber, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityInstanceNumber, entityInstanceNumber, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, "");
+
DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, "");
diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h
index d52c5d9aab..ab5d1d8094 100644
--- a/libraries/entities/src/EntityItemPropertiesDefaults.h
+++ b/libraries/entities/src/EntityItemPropertiesDefaults.h
@@ -26,9 +26,20 @@ const glm::vec3 ENTITY_ITEM_HALF_VEC3 = glm::vec3(0.5f);
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
-const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString("");
const QUuid ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QUuid();
+// Certifiable Properties
+const QString ENTITY_ITEM_DEFAULT_ITEM_NAME = QString("");
+const QString ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION = QString("");
+const QString ENTITY_ITEM_DEFAULT_ITEM_CATEGORIES = QString("");
+const QString ENTITY_ITEM_DEFAULT_ITEM_ARTIST = QString("");
+const QString ENTITY_ITEM_DEFAULT_ITEM_LICENSE = QString("");
+const quint32 ENTITY_ITEM_DEFAULT_LIMITED_RUN = -1;
+const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString("");
+const quint32 ENTITY_ITEM_DEFAULT_EDITION_NUMBER = 0;
+const quint32 ENTITY_ITEM_DEFAULT_ENTITY_INSTANCE_NUMBER = 0;
+const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString("");
+
const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f;
const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f;
const bool ENTITY_ITEM_DEFAULT_VISIBLE = true;
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index d97be6348f..c03630f8bf 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -187,7 +187,19 @@ enum EntityPropertyList {
PROP_SERVER_SCRIPTS,
PROP_FILTER_URL,
-
+
+ // Certificable Properties
+ PROP_ITEM_NAME,
+ PROP_ITEM_DESCRIPTION,
+ PROP_ITEM_CATEGORIES,
+ PROP_ITEM_ARTIST,
+ PROP_ITEM_LICENSE,
+ PROP_LIMITED_RUN,
+ // PROP_MARKETPLACE_ID is above
+ PROP_EDITION_NUMBER,
+ PROP_ENTITY_INSTANCE_NUMBER,
+ PROP_CERTIFICATE_ID,
+
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties to end of list just ABOVE this line
PROP_AFTER_LAST_ITEM,
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index b8396e7357..f5117dddc0 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -1765,3 +1765,31 @@ glm::mat4 EntityScriptingInterface::getEntityLocalTransform(const QUuid& entityI
}
return result;
}
+
+bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& entityID) {
+ bool result = false;
+ if (_entityTree) {
+ _entityTree->withReadLock([&] {
+ EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
+ if (entity) {
+ result = entity->verifyStaticCertificateProperties();
+ }
+ });
+ }
+ return result;
+}
+
+#ifdef DEBUG_CERT
+QString EntityScriptingInterface::computeCertificateID(const QUuid& entityID) {
+ QString result { "" };
+ if (_entityTree) {
+ _entityTree->withReadLock([&] {
+ EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
+ if (entity) {
+ result = entity->computeCertificateID();
+ }
+ });
+ }
+ return result;
+}
+#endif
\ No newline at end of file
diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h
index e594f555d1..989d3dd89d 100644
--- a/libraries/entities/src/EntityScriptingInterface.h
+++ b/libraries/entities/src/EntityScriptingInterface.h
@@ -386,6 +386,11 @@ public slots:
*/
Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID);
+ Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID);
+#ifdef DEBUG_CERT
+ Q_INVOKABLE QString computeCertificateID(const QUuid& entityID);
+#endif
+
signals:
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp
index b02cf04651..14813a68fe 100644
--- a/libraries/entities/src/ModelEntityItem.cpp
+++ b/libraries/entities/src/ModelEntityItem.cpp
@@ -215,16 +215,18 @@ void ModelEntityItem::debugDump() const {
}
void ModelEntityItem::setShapeType(ShapeType type) {
- if (type != _shapeType) {
- if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
- // dynamic and STATIC_MESH are incompatible
- // since the shape is being set here we clear the dynamic bit
- _dynamic = false;
- _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
+ withWriteLock([&] {
+ if (type != _shapeType) {
+ if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
+ // dynamic and STATIC_MESH are incompatible
+ // since the shape is being set here we clear the dynamic bit
+ _dynamic = false;
+ _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
+ }
+ _shapeType = type;
+ _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
}
- _shapeType = type;
- _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
- }
+ });
}
ShapeType ModelEntityItem::getShapeType() const {
@@ -257,13 +259,15 @@ void ModelEntityItem::setModelURL(const QString& url) {
}
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
- if (_compoundShapeURL != url) {
- ShapeType oldType = computeTrueShapeType();
- _compoundShapeURL = url;
- if (oldType != computeTrueShapeType()) {
- _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
+ withWriteLock([&] {
+ if (_compoundShapeURL.get() != url) {
+ ShapeType oldType = computeTrueShapeType();
+ _compoundShapeURL.set(url);
+ if (oldType != computeTrueShapeType()) {
+ _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
+ }
}
- }
+ });
}
void ModelEntityItem::setAnimationURL(const QString& url) {
@@ -492,10 +496,8 @@ bool ModelEntityItem::hasModel() const {
return !_modelURL.isEmpty();
});
}
-bool ModelEntityItem::hasCompoundShapeURL() const {
- return resultWithReadLock([&] {
- return !_compoundShapeURL.isEmpty();
- });
+bool ModelEntityItem::hasCompoundShapeURL() const {
+ return _compoundShapeURL.get().isEmpty();
}
QString ModelEntityItem::getModelURL() const {
@@ -505,9 +507,7 @@ QString ModelEntityItem::getModelURL() const {
}
QString ModelEntityItem::getCompoundShapeURL() const {
- return resultWithReadLock([&] {
- return _compoundShapeURL;
- });
+ return _compoundShapeURL.get();
}
void ModelEntityItem::setColor(const rgbColor& value) {
diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
index c4b3e82f23..7efb493735 100644
--- a/libraries/entities/src/ModelEntityItem.h
+++ b/libraries/entities/src/ModelEntityItem.h
@@ -14,6 +14,7 @@
#include "EntityItem.h"
#include
+#include
#include "AnimationPropertyGroup.h"
class ModelEntityItem : public EntityItem {
@@ -153,7 +154,8 @@ protected:
rgbColor _color;
QString _modelURL;
- QString _compoundShapeURL;
+
+ ThreadSafeValueCache _compoundShapeURL;
AnimationPropertyGroup _animationProperties;
diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp
index 9bbf4323da..c6616f8cd3 100644
--- a/libraries/entities/src/ParticleEffectEntityItem.cpp
+++ b/libraries/entities/src/ParticleEffectEntityItem.cpp
@@ -633,10 +633,12 @@ void ParticleEffectEntityItem::debugDump() const {
}
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
- if (type != _shapeType) {
- _shapeType = type;
- _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
- }
+ withWriteLock([&] {
+ if (type != _shapeType) {
+ _shapeType = type;
+ _dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
+ }
+ });
}
void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) {
diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h
index 14e7cd2f40..c3be53599c 100644
--- a/libraries/entities/src/ZoneEntityItem.h
+++ b/libraries/entities/src/ZoneEntityItem.h
@@ -56,7 +56,7 @@ public:
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
virtual bool isReadyToComputeShape() const override { return false; }
- void setShapeType(ShapeType type) override { _shapeType = type; }
+ void setShapeType(ShapeType type) override { withWriteLock([&] { _shapeType = type; }); }
virtual ShapeType getShapeType() const override;
virtual bool hasCompoundShapeURL() const;
diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp
index b95f2ff6a0..2f3f65c90b 100644
--- a/libraries/networking/src/udt/PacketHeaders.cpp
+++ b/libraries/networking/src/udt/PacketHeaders.cpp
@@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
- return VERSION_ENTITIES_ANIMATION_ALLOW_TRANSLATION_PROPERTIES;
+ return VERSION_ENTITIES_HAS_CERTIFICATE_PROPERTIES;
case PacketType::EntityQuery:
return static_cast(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
case PacketType::AvatarIdentity:
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index f492e42460..122be60870 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -267,6 +267,7 @@ const PacketVersion VERSION_ENTITIES_BULLET_DYNAMICS = 70;
const PacketVersion VERSION_ENTITIES_HAS_SHOULD_HIGHLIGHT = 71;
const PacketVersion VERSION_ENTITIES_HAS_HIGHLIGHT_SCRIPTING_INTERFACE = 72;
const PacketVersion VERSION_ENTITIES_ANIMATION_ALLOW_TRANSLATION_PROPERTIES = 73;
+const PacketVersion VERSION_ENTITIES_HAS_CERTIFICATE_PROPERTIES = 74;
enum class EntityQueryPacketVersion: PacketVersion {
JSONFilter = 18,
diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp
index de5ff1f74c..36663a454a 100644
--- a/libraries/render/src/render/DrawSceneOctree.cpp
+++ b/libraries/render/src/render/DrawSceneOctree.cpp
@@ -151,7 +151,7 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS
float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
Transform crosshairModel;
crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0));
- crosshairModel.setScale(1000.0 * tan(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO
+ crosshairModel.setScale(1000.0f * tanf(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO
batch.resetViewTransform();
batch.setModelTransform(crosshairModel);
batch.setPipeline(getDrawLODReticlePipeline());
diff --git a/libraries/shared/src/shared/Camera.cpp b/libraries/shared/src/shared/Camera.cpp
index 48fea9e835..ab841c4717 100644
--- a/libraries/shared/src/shared/Camera.cpp
+++ b/libraries/shared/src/shared/Camera.cpp
@@ -45,7 +45,7 @@ Camera::Camera() :
{
}
-void Camera::update(float deltaTime) {
+void Camera::update() {
if (_isKeepLookingAt) {
lookAt(_lookingAt);
}
diff --git a/libraries/shared/src/shared/Camera.h b/libraries/shared/src/shared/Camera.h
index c7b943f0dd..3ad08bd719 100644
--- a/libraries/shared/src/shared/Camera.h
+++ b/libraries/shared/src/shared/Camera.h
@@ -53,7 +53,7 @@ public:
void initialize(); // instantly put the camera at the ideal position and orientation.
- void update( float deltaTime );
+ void update();
CameraMode getMode() const { return _mode; }
void setMode(CameraMode m);
diff --git a/libraries/shared/src/shared/RateCounter.h b/libraries/shared/src/shared/RateCounter.h
index 3cf509b6bf..208446e9b1 100644
--- a/libraries/shared/src/shared/RateCounter.h
+++ b/libraries/shared/src/shared/RateCounter.h
@@ -35,19 +35,19 @@ public:
uint32_t interval() const { return INTERVAL; }
private:
- mutable uint64_t _start { usecTimestampNow() };
+ mutable uint64_t _expiry { usecTimestampNow() + INTERVAL * USECS_PER_MSEC};
mutable size_t _count { 0 };
const float _scale { powf(10, PRECISION) };
mutable std::atomic _rate;
void checkRate() const {
auto now = usecTimestampNow();
- float currentIntervalMs = (now - _start) / (float)USECS_PER_MSEC;
- if (currentIntervalMs > (float)INTERVAL) {
- float currentCount = _count;
- float intervalSeconds = currentIntervalMs / (float)MSECS_PER_SECOND;
- _rate = roundf(currentCount / intervalSeconds * _scale) / _scale;
- _start = now;
+ if (now > _expiry) {
+ float MSECS_PER_USEC = 0.001f;
+ float SECS_PER_MSEC = 0.001f;
+ float intervalSeconds = ((float)INTERVAL + (float)(now - _expiry) * MSECS_PER_USEC) * SECS_PER_MSEC;
+ _rate = roundf((float)_count / intervalSeconds * _scale) / _scale;
+ _expiry = now + INTERVAL * USECS_PER_MSEC;
_count = 0;
};
}
diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp
index 9dfe831081..297ed9ca50 100644
--- a/libraries/ui/src/OffscreenUi.cpp
+++ b/libraries/ui/src/OffscreenUi.cpp
@@ -136,6 +136,14 @@ void OffscreenUi::toggle(const QUrl& url, const QString& name, std::functionfindChild(name);
if (item) {
diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h
index 391d7da6c7..93c55d06f7 100644
--- a/libraries/ui/src/OffscreenUi.h
+++ b/libraries/ui/src/OffscreenUi.h
@@ -78,6 +78,7 @@ public:
bool eventFilter(QObject* originalDestination, QEvent* event) override;
void addMenuInitializer(std::function f);
QObject* getFlags();
+ Q_INVOKABLE bool isPointOnDesktopWindow(QVariant point);
QQuickItem* getDesktop();
QQuickItem* getToolWindow();
QObject* getRootMenu();
diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp
index 233a9458fe..f9686d2311 100644
--- a/libraries/ui/src/ui/types/RequestFilters.cpp
+++ b/libraries/ui/src/ui/types/RequestFilters.cpp
@@ -61,7 +61,7 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info)
// During the period in which we have HFC commerce in the system, but not applied everywhere:
const QString tokenStringCommerce{ "Chrome/48.0 (HighFidelityInterface WithHFC)" };
- static Setting::Handle _settingSwitch{ "commerce", false };
+ Setting::Handle _settingSwitch{ "commerce", false };
bool isMoney = _settingSwitch.get();
const QString tokenString = !isAuthable ? tokenStringMobile : (isMoney ? tokenStringCommerce : tokenStringMetaverse);
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 0e58d1ecd3..7553ca4eeb 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -78,6 +78,7 @@
onButtonClicked();
break;
case 'walletReset':
+ Settings.setValue("isFirstUseOfPurchases", true);
onButtonClicked();
onButtonClicked();
break;
diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
index c5b82f75f0..0ef0e67471 100644
--- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
@@ -108,13 +108,17 @@ Script.include("/~/system/libraries/controllers.js");
"userData"
];
-
+ var MARGIN = 25;
function FarActionGrabEntity(hand) {
this.hand = hand;
this.grabbedThingID = null;
this.actionID = null; // action this script created...
this.entityWithContextOverlay = false;
this.contextOverlayTimer = false;
+ this.reticleMinX = MARGIN;
+ this.reticleMaxX;
+ this.reticleMinY = MARGIN;
+ this.reticleMaxY;
var ACTION_TTL = 15; // seconds
@@ -344,12 +348,28 @@ Script.include("/~/system/libraries/controllers.js");
this.grabbedThingID = null;
};
+ this.updateRecommendedArea = function() {
+ var dims = Controller.getViewportDimensions();
+ this.reticleMaxX = dims.x - MARGIN;
+ this.reticleMaxY = dims.y - MARGIN;
+ };
+
+ this.calculateNewReticlePosition = function(intersection) {
+ this.updateRecommendedArea();
+ var point2d = HMD.overlayFromWorldPoint(intersection);
+ point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX));
+ point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY));
+ return point2d;
+ };
+
this.notPointingAtEntity = function(controllerData) {
var intersection = controllerData.rayPicks[this.hand];
var entityProperty = Entities.getEntityProperties(intersection.objectID);
var entityType = entityProperty.type;
+ var hudRayPick = controllerData.hudRayPicks[this.hand];
+ var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web") ||
- intersection.type === RayPick.INTERSECTED_OVERLAY) {
+ intersection.type === RayPick.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) {
return true;
}
return false;
diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js
index 487e491201..912eeccadb 100644
--- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js
+++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js
@@ -178,11 +178,11 @@
}
var hudRayPick = controllerData.hudRayPicks[this.hand];
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
- this.setReticlePosition(point2d);
- if (!Reticle.isPointingAtSystemOverlay(point2d)) {
+ if (!Window.isPointOnDesktopWindow(point2d) && !this.triggerClicked) {
this.exitModule();
return false;
}
+ this.setReticlePosition(point2d);
Reticle.visible = false;
this.movedAway = false;
this.triggerClicked = controllerData.triggerClicks[this.hand];
@@ -239,7 +239,7 @@
function cleanup() {
ControllerDispatcherUtils.disableDispatcherModule("LeftHudOverlayPointer");
- ControllerDispatcherUtils.disbaleDispatcherModule("RightHudOverlayPointer");
+ ControllerDispatcherUtils.disableDispatcherModule("RightHudOverlayPointer");
}
Script.scriptEnding.connect(cleanup);
diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js
index 0c82ab4343..ded4542c51 100644
--- a/scripts/system/html/js/marketplacesInject.js
+++ b/scripts/system/html/js/marketplacesInject.js
@@ -28,6 +28,7 @@
var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free
var userIsLoggedIn = false;
+ var walletNeedsSetup = false;
function injectCommonCode(isDirectoryPage) {
@@ -91,6 +92,48 @@
});
}
+ emitWalletSetupEvent = function() {
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: "WALLET_SETUP"
+ }));
+ }
+
+ function maybeAddSetupWalletButton() {
+ if (userIsLoggedIn && walletNeedsSetup) {
+ var resultsElement = document.getElementById('results');
+ var setupWalletElement = document.createElement('div');
+ setupWalletElement.classList.add("row");
+ setupWalletElement.id = "setupWalletDiv";
+ setupWalletElement.style = "height:60px;margin:20px 10px 10px 10px;padding:12px 5px;" +
+ "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;";
+
+ var span = document.createElement('span');
+ span.style = "margin:10px 5px;color:#1b6420;font-size:15px;";
+ span.innerHTML = "Setup your Wallet to get money and shop in Marketplace.";
+
+ var xButton = document.createElement('a');
+ xButton.id = "xButton";
+ xButton.setAttribute('href', "#");
+ xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;";
+ xButton.innerHTML = "X";
+ xButton.onclick = function () {
+ setupWalletElement.remove();
+ dummyRow.remove();
+ };
+
+ setupWalletElement.appendChild(span);
+ setupWalletElement.appendChild(xButton);
+
+ resultsElement.insertBefore(setupWalletElement, resultsElement.firstChild);
+
+ // Dummy row for padding
+ var dummyRow = document.createElement('div');
+ dummyRow.classList.add("row");
+ dummyRow.style = "height:15px;";
+ resultsElement.insertBefore(dummyRow, resultsElement.firstChild);
+ }
+ }
+
function maybeAddLogInButton() {
if (!userIsLoggedIn) {
var resultsElement = document.getElementById('results');
@@ -149,7 +192,7 @@
var dropDownElement = document.getElementById('user-dropdown');
purchasesElement.id = "purchasesButton";
purchasesElement.setAttribute('href', "#");
- purchasesElement.innerHTML = "MY PURCHASES";
+ purchasesElement.innerHTML = "My Purchases";
// FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same
// line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px".
purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) +
@@ -164,12 +207,34 @@
}
}
+ function changeDropdownMenu() {
+ var logInOrOutButton = document.createElement('a');
+ logInOrOutButton.id = "logInOrOutButton";
+ logInOrOutButton.setAttribute('href', "#");
+ logInOrOutButton.innerHTML = userIsLoggedIn ? "Log Out" : "Log In";
+ logInOrOutButton.onclick = function () {
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: "LOGIN"
+ }));
+ };
+
+ $($('.dropdown-menu').find('li')[0]).append(logInOrOutButton);
+
+ $('a[href="/marketplace?view=mine"]').each(function () {
+ $(this).attr('href', '#');
+ $(this).on('click', function () {
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: "MY_ITEMS"
+ }));
+ });
+ });
+ }
+
function buyButtonClicked(id, name, author, price, href) {
EventBridge.emitWebEvent(JSON.stringify({
type: "CHECKOUT",
itemId: id,
itemName: name,
- itemAuthor: author,
itemPrice: price ? parseInt(price, 10) : 0,
itemHref: href
}));
@@ -235,9 +300,13 @@
}
function injectHiFiCode() {
- if (confirmAllPurchases) {
+ if (!$('body').hasClass("code-injected") && confirmAllPurchases) {
+
+ $('body').addClass("code-injected");
maybeAddLogInButton();
+ maybeAddSetupWalletButton();
+ changeDropdownMenu();
var target = document.getElementById('templated-items');
// MutationObserver is necessary because the DOM is populated after the page is loaded.
@@ -260,9 +329,13 @@
}
function injectHiFiItemPageCode() {
- if (confirmAllPurchases) {
+ if (!$('body').hasClass("code-injected") && confirmAllPurchases) {
+
+ $('body').addClass("code-injected");
maybeAddLogInButton();
+ maybeAddSetupWalletButton();
+ changeDropdownMenu();
var purchaseButton = $('#side-info').find('.btn').first();
@@ -551,7 +624,8 @@
if (parsedJsonMessage.type === "marketplaces") {
if (parsedJsonMessage.action === "commerceSetting") {
confirmAllPurchases = !!parsedJsonMessage.data.commerceMode;
- userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn
+ userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn;
+ walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup;
injectCode();
}
}
@@ -567,4 +641,5 @@
// Load / unload.
window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
+ window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed
}());
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 5a00b20441..e94b227a4a 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -19,7 +19,8 @@
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
- var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/checkout/Checkout.qml";
+ var MARKETPLACE_CHECKOUT_QML_PATH_BASE = "qml/hifi/commerce/checkout/Checkout.qml";
+ var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + MARKETPLACE_CHECKOUT_QML_PATH_BASE;
var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml";
var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml";
var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
@@ -60,6 +61,7 @@
var onCommerceScreen = false;
var debugCheckout = false;
+ var debugError = false;
function showMarketplace() {
if (!debugCheckout) {
UserActivityLogger.openedMarketplace();
@@ -70,8 +72,7 @@
method: 'updateCheckoutQML', params: {
itemId: '0d90d21c-ce7a-4990-ad18-e9d2cf991027',
itemName: 'Test Flaregun',
- itemAuthor: 'hifiDave',
- itemPrice: 17,
+ itemPrice: (debugError ? 10 : 17),
itemHref: 'http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json',
},
canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified
@@ -106,8 +107,8 @@
var referrerURL; // Used for updating Purchases QML
var filterText; // Used for updating Purchases QML
function onScreenChanged(type, url) {
- onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL;
- onCommerceScreen = type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
+ onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
+ onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
wireEventBridge(onCommerceScreen);
if (url === MARKETPLACE_PURCHASES_QML_PATH) {
@@ -128,12 +129,11 @@
}
}
- function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId, closeGoesToPurchases) {
+ function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId) {
wireEventBridge(true);
tablet.sendToQml({
method: 'inspectionCertificate_setMarketplaceId',
- marketplaceId: itemMarketplaceId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['marketplaceID']).marketplaceID,
- closeGoesToPurchases: closeGoesToPurchases
+ marketplaceId: itemMarketplaceId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['marketplaceID']).marketplaceID
});
// ZRF FIXME! Make a call to the endpoint to get item info instead of this silliness
Script.setTimeout(function () {
@@ -203,7 +203,8 @@
action: "commerceSetting",
data: {
commerceMode: Settings.getValue("commerce", false),
- userIsLoggedIn: Account.loggedIn
+ userIsLoggedIn: Account.loggedIn,
+ walletNeedsSetup: Wallet.walletStatus === 1
}
}));
} else if (parsedJsonMessage.type === "PURCHASES") {
@@ -212,6 +213,16 @@
tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
} else if (parsedJsonMessage.type === "LOGIN") {
openLoginWindow();
+ } else if (parsedJsonMessage.type === "WALLET_SETUP") {
+ tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH);
+ } else if (parsedJsonMessage.type === "MY_ITEMS") {
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = "";
+ tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
+ wireEventBridge(true);
+ tablet.sendToQml({
+ method: 'purchases_showMyItems'
+ });
}
}
}
@@ -325,36 +336,26 @@
case 'maybeEnableHmdPreview':
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break;
- case 'purchases_getIsFirstUse':
- tablet.sendToQml({
- method: 'purchases_getIsFirstUseResult',
- isFirstUseOfPurchases: Settings.getValue("isFirstUseOfPurchases", true)
- });
- break;
- case 'purchases_setIsFirstUse':
- Settings.setValue("isFirstUseOfPurchases", false);
- break;
case 'purchases_openGoTo':
tablet.loadQMLSource("TabletAddressDialog.qml");
break;
case 'purchases_itemCertificateClicked':
- tablet.loadQMLSource("../commerce/inspectionCertificate/InspectionCertificate.qml");
- setCertificateInfo("", message.itemMarketplaceId, true);
+ setCertificateInfo("", message.itemMarketplaceId);
break;
case 'inspectionCertificate_closeClicked':
- if (message.closeGoesToPurchases) {
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- } else {
- tablet.gotoHomeScreen();
- }
+ tablet.gotoHomeScreen();
break;
case 'inspectionCertificate_showInMarketplaceClicked':
tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
break;
case 'header_myItemsClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL + '?view=mine', MARKETPLACES_INJECT_SCRIPT_URL);
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = "";
+ tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
+ wireEventBridge(true);
+ tablet.sendToQml({
+ method: 'purchases_showMyItems'
+ });
break;
default:
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));