Merging with master

This commit is contained in:
samcake 2017-10-04 10:55:38 -07:00
commit d7333d3fa3
68 changed files with 1809 additions and 791 deletions
interface
libraries
scripts/system

View file

@ -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

View file

@ -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
}

View file

@ -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:<br><br>" + (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 " + '<font color="' + hifi.colors.blueAccent + '"><a href="#">' + itemNameText.text + '</a></font>' +
text: "The item " + '<font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
" has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
// Text 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.<br>" +
'<font color="' + hifi.colors.blueAccent + '"><a href="#">View the copy you own in My Purchases</a></font>';
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.<br>" +
'<font color="' + hifi.colors.blueAccent + '"><a href="#">View the copy you own in My Purchases</a></font></b>';
} else {
buyText.text = "Your Wallet does not have sufficient funds to purchase this item.";
buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
}
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.<br>Purchasing it will buy another copy.<br><font color="'
+ hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font>';
buyText.text = '<b>You already own this item.<br>Purchasing it will buy another copy.<br><font color="'
+ hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font></b>';
buyTextContainer.color = "#FFD6AD";
buyTextContainer.border.color = "#FAC07D";
buyGlyph.text = hifi.glyphs.alert;
@ -870,7 +878,7 @@ Rectangle {
buyText.text = "";
}
} else {
buyText.text = "This Marketplace item isn't an entity. It <b>will not</b> be added to your <b>Purchases</b>.";
buyText.text = "This free item <b>will not</b> be added to your <b>Purchases</b>. 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();
}
}
//

View file

@ -82,6 +82,7 @@ Rectangle {
height: 140;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
}
RalewayRegular {

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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;

View file

@ -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: "<b>This is the first-time Purchases tutorial.</b><br><br>Here is some <b>bold text</b> " +
"inside Step 1.";
text: "The <b>'REZ IT'</b> 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: "<b>STEP TWOOO!!!</b>";
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
//

View file

@ -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) {
"<font color='#6a6a6a'>(#" + root.itemEdition + ")</font> <u>You own " + root.ownedItemCount + " others</u>"
} 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;
}
}
}

View file

@ -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: "<b>You haven't submitted anything to the Marketplace yet!</b><br><br>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));

Binary file not shown.

After

(image error) Size: 72 KiB

Binary file not shown.

After

(image error) Size: 81 KiB

View file

@ -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();
}
}
}

View file

@ -129,6 +129,7 @@ Item {
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
MouseArea {
enabled: titleBarSecurityImage.visible;

View file

@ -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);
}

View file

@ -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();
}
}
}

View file

@ -65,6 +65,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
}
}
MouseArea {

View file

@ -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();
}
}

View file

@ -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 + '<br>' + 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()
//

View file

@ -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('/')));
}
}
}

View file

@ -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

View file

@ -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<ContextOverlayInterface>();
DependencyManager::set<Ledger>();
DependencyManager::set<Wallet>();
DependencyManager::set<WalletScriptingInterface>();
DependencyManager::set<FadeEffect>();
@ -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<SelectionScriptingInterface>().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().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<StatTracker>()->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<UserInputMapper>();
auto myAvatar = getMyAvatar();
auto userInputMapper = DependencyManager::get<UserInputMapper>();
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<controller::Action> 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<controller::Action> 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> avatarManager = DependencyManager::get<AvatarManager>();
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<SelectionScriptingInterface>().data());
scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);

View file

@ -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;

View file

@ -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<FramebufferCache>()->releaseFramebuffer(framebuffer);
@ -152,7 +152,7 @@ void Application::paintGL() {
{
PROFILE_RANGE(render, "/pluginOutput");
PerformanceTimer perfTimer("pluginOutput");
_frameCounter.increment();
_renderLoopCounter.increment();
displayPlugin->submitFrame(frame);
}

View file

@ -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) {

View file

@ -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());

View file

@ -15,6 +15,7 @@
#include "Ledger.h"
#include "Wallet.h"
#include <AccountManager.h>
#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<Wallet>();
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
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<WalletScriptingInterface>();
uint status;
if (DependencyManager::get<AccountManager>()->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>();
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<Wallet>();
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<Wallet>();
if(wallet->getPassphrase() && !wallet->getPassphrase()->isEmpty() && !passphrase.isEmpty()) {
wallet->changePassphrase(passphrase);
} else {
wallet->setPassphrase(passphrase);
}
wallet->setPassphrase(passphrase);
getWalletAuthenticatedStatus();
}

View file

@ -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();

View file

@ -284,7 +284,7 @@ Wallet::Wallet() {
auto nodeList = DependencyManager::get<NodeList>();
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 "";
}
}

View file

@ -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; }

View file

@ -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;

View file

@ -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

View file

@ -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<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
tablet->loadQMLSource(CHECKOUT_QML_PATH);
DependencyManager::get<HMDScriptingInterface>()->openTablet();
QQuickItem* root = nullptr;
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) {
root = DependencyManager::get<OffscreenUi>()->getRootItem();
} else {
root = tablet->getTabletRoot();
}
CheckoutProxy* checkout = new CheckoutProxy(root->findChild<QObject*>("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);
}

View file

@ -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 <QtCore/QObject>
#include <DependencyManager.h>
#include "scripting/HMDScriptingInterface.h"
#include <ui/TabletScriptingInterface.h>
#include <ui/QmlWrapper.h>
#include <OffscreenUi.h>
#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

View file

@ -171,6 +171,11 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc
Setting::Handle<QVariant>(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location);
}
bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
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 {

View file

@ -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);

View file

@ -8,6 +8,7 @@
#include "Stats.h"
#include <queue>
#include <sstream>
#include <QFontDatabase>
@ -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<NodeList>();
auto avatarManager = DependencyManager::get<AvatarManager>();
// 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<BandwidthRecorder>();
STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());
@ -406,14 +399,21 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get<LODManager>()->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<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
std::priority_queue<SortableStat> 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<QString> 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) {

View file

@ -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();

View file

@ -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<bool> _settingSwitch{ "commerce", false };
if (_settingSwitch.get()) {
openInspectionCertificate();
} else {
openMarketplace();

View file

@ -79,8 +79,6 @@ private:
bool _isInMarketplaceInspectionMode { false };
Setting::Handle<bool> _commerceSettingSwitch{ "commerce", false };
void openInspectionCertificate();
void openMarketplace();
void enableEntityHighlight(const EntityItemID& entityItemID);

View file

@ -129,10 +129,10 @@ protected:
bool _vsyncEnabled { true };
QThread* _presentThread{ nullptr };
std::queue<gpu::FramePointer> _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 };

View file

@ -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)

View file

@ -13,6 +13,10 @@
#include <QtCore/QObject>
#include <QtEndian>
#include <QJsonDocument>
#include <openssl/rsa.h> // see comments for DEBUG_CERT
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <glm/gtx/transform.hpp>
@ -34,6 +38,7 @@
Q_DECLARE_METATYPE(EntityItemPointer);
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
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<const unsigned char*>(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<unsigned char*>(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<const unsigned char*>(signatureBytes.constData());
const unsigned int signatureLength = signatureBytes.length();
const auto hash = getStaticCertificateHash();
const auto text = reinterpret_cast<const unsigned char*>(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;

View file

@ -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).

View file

@ -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<QString> 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";
}

View file

@ -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, "");

View file

@ -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;

View file

@ -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,

View file

@ -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

View file

@ -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);

View file

@ -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<bool>([&] {
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<QString>([&] {
return _compoundShapeURL;
});
return _compoundShapeURL.get();
}
void ModelEntityItem::setColor(const rgbColor& value) {

View file

@ -14,6 +14,7 @@
#include "EntityItem.h"
#include <JointData.h>
#include <ThreadSafeValueCache.h>
#include "AnimationPropertyGroup.h"
class ModelEntityItem : public EntityItem {
@ -153,7 +154,8 @@ protected:
rgbColor _color;
QString _modelURL;
QString _compoundShapeURL;
ThreadSafeValueCache<QString> _compoundShapeURL;
AnimationPropertyGroup _animationProperties;

View file

@ -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) {

View file

@ -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;

View file

@ -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<PacketVersion>(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
case PacketType::AvatarIdentity:

View file

@ -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,

View file

@ -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());

View file

@ -45,7 +45,7 @@ Camera::Camera() :
{
}
void Camera::update(float deltaTime) {
void Camera::update() {
if (_isKeepLookingAt) {
lookAt(_lookingAt);
}

View file

@ -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);

View file

@ -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<float> _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;
};
}

View file

@ -136,6 +136,14 @@ void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<voi
shownProperty.write(!shownProperty.read().toBool());
}
bool OffscreenUi::isPointOnDesktopWindow(QVariant point) {
QVariant result;
BLOCKING_INVOKE_METHOD(_desktop, "isPointOnWindow",
Q_RETURN_ARG(QVariant, result),
Q_ARG(QVariant, point));
return result.toBool();
}
void OffscreenUi::hide(const QString& name) {
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
if (item) {

View file

@ -78,6 +78,7 @@ public:
bool eventFilter(QObject* originalDestination, QEvent* event) override;
void addMenuInitializer(std::function<void(VrMenu*)> f);
QObject* getFlags();
Q_INVOKABLE bool isPointOnDesktopWindow(QVariant point);
QQuickItem* getDesktop();
QQuickItem* getToolWindow();
QObject* getRootMenu();

View file

@ -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<bool> _settingSwitch{ "commerce", false };
Setting::Handle<bool> _settingSwitch{ "commerce", false };
bool isMoney = _settingSwitch.get();
const QString tokenString = !isAuthable ? tokenStringMobile : (isMoney ? tokenStringCommerce : tokenStringMetaverse);

View file

@ -78,6 +78,7 @@
onButtonClicked();
break;
case 'walletReset':
Settings.setValue("isFirstUseOfPurchases", true);
onButtonClicked();
onButtonClicked();
break;

View file

@ -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;

View file

@ -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);

View file

@ -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 = "<a href='#' onclick='emitWalletSetupEvent(); return false;'>Setup your Wallet</a> 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
}());

View file

@ -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));