diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index ca7226a6ab..579b4e7fd6 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -50,7 +50,22 @@ FocusScope { property bool desktopRoot: true // The VR version of the primary menu - property var rootMenu: Menu { objectName: "rootMenu" } + property var rootMenu: Menu { + objectName: "rootMenu" + + // for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot + property var exclusionGroupsByMenuItem : ListModel {} + + function addExclusionGroup(menuItem, exclusionGroup) + { + exclusionGroupsByMenuItem.append( + { + 'menuItem' : menuItem.toString(), + 'exclusionGroup' : exclusionGroup.toString() + } + ); + } + } // FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD // because shaders are 4.2, and do not include #version declarations. diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml deleted file mode 100644 index d9b0072917..0000000000 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ /dev/null @@ -1,463 +0,0 @@ -// -// Checkout.qml -// qml/hifi/commerce -// -// Checkout -// -// Created by Zach Fox on 2017-08-07 -// 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 Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls -import "./wallet" as HifiWallet - -// references XXX from root context - -Rectangle { - HifiConstants { id: hifi; } - - id: checkoutRoot; - property bool inventoryReceived: false; - property bool balanceReceived: false; - property string itemId: ""; - property string itemHref: ""; - property int balanceAfterPurchase: 0; - property bool alreadyOwned: false; - property int itemPriceFull: 0; - // Style - color: hifi.colors.baseGray; - Hifi.QmlCommerce { - id: commerce; - onBuyResult: { - if (result.status !== 'success') { - buyButton.text = result.message; - buyButton.enabled = false; - } else { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); - } - sendToScript({method: 'checkout_buySuccess', itemId: itemId}); - } - } - onBalanceResult: { - if (result.status !== 'success') { - console.log("Failed to get balance", result.message); - } else { - balanceReceived = true; - hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2); - balanceAfterPurchase = parseFloat(result.data.balance/100) - parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2); - } - } - onInventoryResult: { - if (result.status !== 'success') { - console.log("Failed to get inventory", result.message); - } else { - inventoryReceived = true; - console.log('inventory fixme', JSON.stringify(result)); - if (inventoryContains(result.data.assets, itemId)) { - alreadyOwned = true; - } else { - alreadyOwned = false; - } - } - } - } - - // - // TITLE BAR START - // - Item { - id: titleBarContainer; - // Size - width: checkoutRoot.width; - height: 50; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - // Title Bar text - RalewaySemiBold { - id: titleBarText; - text: "Checkout"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Separator - HifiControlsUit.Separator { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - } - } - // - // TITLE BAR END - // - - // - // ITEM DESCRIPTION START - // - Item { - id: itemDescriptionContainer; - // Size - width: checkoutRoot.width; - height: childrenRect.height + 20; - // Anchors - anchors.left: parent.left; - anchors.top: titleBarContainer.bottom; - - // Item Name text - Item { - id: itemNameContainer; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: itemNameTextLabel; - text: "Item Name:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 16; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: itemNameText; - // Text size - size: itemNameTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: itemNameTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - - // Item Author text - Item { - id: itemAuthorContainer; - // Anchors - anchors.top: itemNameContainer.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: itemAuthorTextLabel; - text: "Item Author:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 16; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: itemAuthorText; - // Text size - size: itemAuthorTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: itemAuthorTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // HFC Balance text - Item { - id: hfcBalanceContainer; - // Anchors - anchors.top: itemAuthorContainer.bottom; - anchors.topMargin: 16; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: hfcBalanceTextLabel; - text: "HFC Balance:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: hfcBalanceText; - text: "--"; - // Text size - size: hfcBalanceTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: hfcBalanceTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // Item Price text - Item { - id: itemPriceContainer; - // Anchors - anchors.top: hfcBalanceContainer.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: itemPriceTextLabel; - text: "Item Price:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: itemPriceText; - // Text size - size: itemPriceTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: itemPriceTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // HFC "Balance After Purchase" text - Item { - id: hfcBalanceAfterPurchaseContainer; - // Anchors - anchors.top: itemPriceContainer.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: hfcBalanceAfterPurchaseTextLabel; - text: "HFC Balance After Purchase:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: hfcBalanceAfterPurchaseText; - text: balanceAfterPurchase; - // Text size - size: hfcBalanceAfterPurchaseTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: hfcBalanceAfterPurchaseTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - } - // - // ITEM DESCRIPTION END - // - - - // - // ACTION BUTTONS START - // - Item { - id: actionButtonsContainer; - // Size - width: checkoutRoot.width; - height: 40; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 8; - - // "Cancel" button - HifiControlsUit.Button { - id: cancelButton; - 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: "Cancel" - onClicked: { - sendToScript({method: 'checkout_cancelClicked', params: itemId}); - } - } - - // "Buy" button - HifiControlsUit.Button { - id: buyButton; - enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived; - color: hifi.buttons.black; - 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: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--"; - onClicked: { - if (!alreadyOwned) { - commerce.buy(itemId, parseFloat(itemPriceText.text*100)); - } else { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); - } - sendToScript({method: 'checkout_buySuccess', itemId: itemId}); - } - } - } - } - // - // ACTION BUTTONS END - // - - // - // 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 'updateCheckoutQML': - itemId = message.params.itemId; - itemNameText.text = message.params.itemName; - itemAuthorText.text = message.params.itemAuthor; - checkoutRoot.itemPriceFull = message.params.itemPrice; - itemPriceText.text = parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2); - itemHref = message.params.itemHref; - commerce.balance(); - commerce.inventory(); - break; - default: - console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); - } - } - signal sendToScript(var message); - - function inventoryContains(inventoryJson, id) { - for (var idx = 0; idx < inventoryJson.length; idx++) { - if(inventoryJson[idx].id === id) { - return true; - } - } - return false; - } - - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml deleted file mode 100644 index 20458f9f16..0000000000 --- a/interface/resources/qml/hifi/commerce/Inventory.qml +++ /dev/null @@ -1,280 +0,0 @@ -// -// Inventory.qml -// qml/hifi/commerce -// -// Inventory -// -// Created by Zach Fox on 2017-08-10 -// 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 Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls -import "./wallet" as HifiWallet - -// references XXX from root context - -Rectangle { - HifiConstants { id: hifi; } - - id: inventoryRoot; - property string referrerURL: ""; - // Style - color: hifi.colors.baseGray; - Hifi.QmlCommerce { - id: commerce; - onBalanceResult: { - if (result.status !== 'success') { - console.log("Failed to get balance", result.message); - } else { - hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2); - } - } - onInventoryResult: { - if (result.status !== 'success') { - console.log("Failed to get inventory", result.message); - } else { - inventoryContentsList.model = result.data.assets; - } - } - } - - // - // TITLE BAR START - // - Item { - id: titleBarContainer; - // Size - width: parent.width; - height: 50; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - // Title Bar text - RalewaySemiBold { - id: titleBarText; - text: "Inventory"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Separator - HifiControlsUit.Separator { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - } - } - // - // TITLE BAR END - // - - // - // HFC BALANCE START - // - Item { - id: hfcBalanceContainer; - // Size - width: inventoryRoot.width; - height: childrenRect.height + 20; - // Anchors - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: 4; - - RalewaySemiBold { - id: hfcBalanceTextLabel; - text: "HFC Balance:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: hfcBalanceText; - text: "--"; - // Text size - size: hfcBalanceTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: hfcBalanceTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - // - // HFC BALANCE END - // - - // - // INVENTORY CONTENTS START - // - Item { - id: inventoryContentsContainer; - // Anchors - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - anchors.top: hfcBalanceContainer.bottom; - anchors.topMargin: 8; - anchors.bottom: actionButtonsContainer.top; - anchors.bottomMargin: 8; - - RalewaySemiBold { - id: inventoryContentsLabel; - text: "Inventory:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 24; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - ListView { - id: inventoryContentsList; - clip: true; - // Anchors - anchors.top: inventoryContentsLabel.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: parent.width; - delegate: Item { - width: parent.width; - height: 30; - RalewayRegular { - id: thisItemId; - // Text size - size: 20; - // Style - color: hifi.colors.blueAccent; - text: modelData.title; - // Alignment - horizontalAlignment: Text.AlignHLeft; - } - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - sendToScript({method: 'inventory_itemClicked', itemId: modelData.id}); - } - onEntered: { - thisItemId.color = hifi.colors.blueHighlight; - } - onExited: { - thisItemId.color = hifi.colors.blueAccent; - } - } - } - } - } - // - // INVENTORY CONTENTS END - // - - // - // ACTION BUTTONS START - // - Item { - id: actionButtonsContainer; - // Size - width: inventoryRoot.width; - height: 40; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 8; - - // "Back" button - HifiControlsUit.Button { - id: backButton; - 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: "Back" - onClicked: { - sendToScript({method: 'inventory_backClicked', referrerURL: referrerURL}); - } - } - } - // - // ACTION BUTTONS END - // - - // - // 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 'updateInventory': - referrerURL = message.referrerURL; - commerce.balance(); - commerce.inventory(); - break; - default: - console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); - } - } - signal sendToScript(var message); - - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml new file mode 100644 index 0000000000..5329099df5 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -0,0 +1,902 @@ +// +// Checkout.qml +// qml/hifi/commerce/checkout +// +// Checkout +// +// Created by Zach Fox on 2017-08-25 +// 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 Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls +import "../wallet" as HifiWallet + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string activeView: "initialize"; + property bool purchasesReceived: false; + property bool balanceReceived: false; + property string itemId: ""; + property string itemHref: ""; + property double balanceAfterPurchase: 0; + property bool alreadyOwned: false; + property int itemPriceFull: 0; + property bool itemIsJson: true; + property bool securityImageResultReceived: false; + property bool keyFilePathIfExistsResultReceived: false; + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + securityImageResultReceived = true; + if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { + root.activeView = "checkoutMain"; + } else if (exists) { + // just set the source again (to be sure the change was noticed) + securityImage.source = ""; + securityImage.source = "image://security/securityImage"; + } + } + + onKeyFilePathIfExistsResult: { + keyFilePathIfExistsResultReceived = true; + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + root.activeView = "checkoutMain"; + } + } + + onBuyResult: { + if (result.status !== 'success') { + failureErrorText.text = "Here's some more info about the error:

" + (result.message); + root.activeView = "checkoutFailure"; + } else { + root.activeView = "checkoutSuccess"; + } + } + + onBalanceResult: { + if (result.status !== 'success') { + console.log("Failed to get balance", result.data.message); + } else { + root.balanceReceived = true; + hfcBalanceText.text = (parseFloat(result.data.balance/100).toFixed(2)) + " HFC"; + balanceAfterPurchase = parseFloat(result.data.balance/100) - root.itemPriceFull/100; + root.setBuyText(); + } + } + + onInventoryResult: { + if (result.status !== 'success') { + console.log("Failed to get purchases", result.data.message); + } else { + root.purchasesReceived = true; + if (purchasesContains(result.data.assets, itemId)) { + root.alreadyOwned = true; + } else { + root.alreadyOwned = false; + } + root.setBuyText(); + } + } + } + + HifiWallet.SecurityImageModel { + id: securityImageModel; + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "MARKETPLACE"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Security Image (TEMPORARY!) + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height - 10; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + source: "image://security/securityImage"; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + Rectangle { + id: initialize; + visible: root.activeView === "initialize"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.baseGray; + + Component.onCompleted: { + commerce.getSecurityImage(); + commerce.getKeyFilePathIfExists(); + } + } + + // + // "WALLET NOT SET UP" START + // + Item { + id: notSetUp; + visible: root.activeView === "notSetUp"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: notSetUpText; + text: "Your wallet isn't set up.

Set up your Wallet (no credit card necessary) to claim your free HFC " + + "and get items from the Marketplace."; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.bottom: notSetUpActionButtonsContainer.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: notSetUpActionButtonsContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 24; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelButton; + 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: "Cancel" + onClicked: { + sendToScript({method: 'checkout_cancelClicked', params: itemId}); + } + } + + // "Set Up" button + HifiControlsUit.Button { + id: setUpButton; + 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: "Set Up Wallet" + onClicked: { + sendToScript({method: 'checkout_setUpClicked'}); + } + } + } + } + // + // "WALLET NOT SET UP" END + // + + // + // CHECKOUT CONTENTS START + // + Item { + id: checkoutContents; + visible: root.activeView === "checkoutMain"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + onVisibleChanged: { + if (visible) { + commerce.balance(); + commerce.inventory(); + } + } + + // + // ITEM DESCRIPTION START + // + Item { + id: itemDescriptionContainer; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 32; + anchors.right: parent.right; + anchors.rightMargin: 32; + anchors.top: parent.top; + anchors.bottom: checkoutActionButtonsContainer.top; + + // HFC Balance text + Item { + id: hfcBalanceContainer; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: hfcBalanceTextLabel; + text: "Balance:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 30; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: hfcBalanceText; + text: "-- HFC"; + // Text size + size: hfcBalanceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: hfcBalanceTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + // Item Name text + Item { + id: itemNameContainer; + // Anchors + anchors.top: hfcBalanceContainer.bottom; + anchors.topMargin: 32; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemNameTextLabel; + text: "Item:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemNameText; + // Text size + size: itemNameTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemNameTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.lightGrayText; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + + // Item Author text + Item { + id: itemAuthorContainer; + // Anchors + anchors.top: itemNameContainer.bottom; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemAuthorTextLabel; + text: "Author:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemAuthorText; + // Text size + size: itemAuthorTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemAuthorTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.lightGrayText; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + // "Item Price" container + Item { + id: itemPriceContainer; + // Anchors + anchors.top: itemAuthorContainer.bottom; + anchors.topMargin: 32; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemPriceTextLabel; + text: "Item Price:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 30; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemPriceText; + text: "-- HFC"; + // Text size + size: itemPriceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemPriceTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + // "Balance After Purchase" container + Item { + id: balanceAfterPurchaseContainer; + // Anchors + anchors.top: itemPriceContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: balanceAfterPurchaseTextLabel; + text: "Balance After Purchase:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: balanceAfterPurchaseText; + text: balanceAfterPurchase.toFixed(2) + " HFC"; + // Text size + size: balanceAfterPurchaseTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: balanceAfterPurchaseTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + } + // + // ITEM DESCRIPTION END + // + + + // + // ACTION BUTTONS AND TEXT START + // + Item { + id: checkoutActionButtonsContainer; + // Size + width: root.width; + height: 200; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelPurchaseButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + height: 40; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + sendToScript({method: 'checkout_cancelClicked', params: itemId}); + } + } + + // "Buy" button + HifiControlsUit.Button { + id: buyButton; + enabled: (balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + height: 40; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); + onClicked: { + if (itemIsJson) { + buyButton.enabled = false; + commerce.buy(itemId, itemPriceFull); + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + } + } + } + + // "Purchases" button + HifiControlsUit.Button { + id: goToPurchasesButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: buyButton.bottom; + anchors.topMargin: 20; + anchors.bottomMargin: 7; + height: 40; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width - anchors.leftMargin*2; + text: "View Purchases" + onClicked: { + sendToScript({method: 'checkout_goToPurchases'}); + } + } + + RalewayRegular { + id: buyText; + // Text size + size: 20; + // Anchors + anchors.bottom: parent.bottom; + anchors.bottomMargin: 10; + height: paintedHeight; + anchors.left: parent.left; + anchors.leftMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + // + // ACTION BUTTONS END + // + } + // + // CHECKOUT CONTENTS END + // + + // + // CHECKOUT SUCCESS START + // + Item { + id: checkoutSuccess; + visible: root.activeView === "checkoutSuccess"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: root.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: completeText; + text: "Purchase Complete!

You bought " + (itemNameText.text) + " by " + (itemAuthorText.text) + ""; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 40; + height: paintedHeight; + anchors.left: parent.left; + anchors.right: parent.right; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: checkoutSuccessActionButtonsContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.top: completeText.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + + // "Purchases" button + HifiControlsUit.Button { + id: purchasesButton; + 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: "View Purchases"; + onClicked: { + sendToScript({method: 'checkout_goToPurchases'}); + } + } + + // "Rez Now!" button + HifiControlsUit.Button { + id: rezNowButton; + 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: "Rez Now!" + onClicked: { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + } + } + } + + Item { + id: continueShoppingButtonContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + // "Continue Shopping" button + HifiControlsUit.Button { + id: continueShoppingButton; + 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.rightMargin*2; + text: "Continue Shopping"; + onClicked: { + sendToScript({method: 'checkout_continueShopping', itemId: itemId}); + } + } + } + } + // + // CHECKOUT SUCCESS END + // + + // + // CHECKOUT FAILURE START + // + Item { + id: checkoutFailure; + visible: root.activeView === "checkoutFailure"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: root.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: failureHeaderText; + text: "Purchase Failed.
Your Purchases and HFC balance haven't changed."; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 80; + height: paintedHeight; + anchors.left: parent.left; + anchors.right: parent.right; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + RalewayRegular { + id: failureErrorText; + // Text size + size: 16; + // Anchors + anchors.top: failureHeaderText.bottom; + anchors.topMargin: 35; + height: paintedHeight; + anchors.left: parent.left; + anchors.right: parent.right; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: backToMarketplaceButtonContainer; + // Size + width: root.width; + height: 130; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + // "Back to Marketplace" button + HifiControlsUit.Button { + id: backToMarketplaceButton; + color: hifi.buttons.black; + 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: "Back to Marketplace"; + onClicked: { + sendToScript({method: 'checkout_continueShopping', itemId: itemId}); + } + } + } + } + // + // CHECKOUT FAILURE END + // + + // + // 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 'updateCheckoutQML': + itemId = message.params.itemId; + itemNameText.text = message.params.itemName; + itemAuthorText.text = message.params.itemAuthor; + root.itemPriceFull = message.params.itemPrice; + itemPriceText.text = root.itemPriceFull === 0 ? "Free" : "" + (parseFloat(root.itemPriceFull/100).toFixed(2)) + " HFC"; + itemHref = message.params.itemHref; + if (itemHref.indexOf('.json') === -1) { + root.itemIsJson = false; + } + setBuyText(); + break; + default: + console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + function purchasesContains(purchasesJson, id) { + for (var idx = 0; idx < purchasesJson.length; idx++) { + if(purchasesJson[idx].id === id) { + return true; + } + } + return false; + } + + function setBuyText() { + if (root.itemIsJson) { + if (root.purchasesReceived && root.balanceReceived) { + if (root.balanceAfterPurchase < 0) { + if (root.alreadyOwned) { + buyText.text = "You do not have enough HFC to purchase this item again. Go to your Purchases to view the copy you own."; + } else { + buyText.text = "You do not have enough HFC to purchase this item."; + } + } else { + if (root.alreadyOwned) { + buyText.text = "You already own this item. If you buy it again, you'll be able to use multiple copies of it at once."; + } else { + buyText.text = "This item will be added to your Purchases, which can be accessed from Marketplace."; + } + } + } else { + buyText.text = ""; + } + } else { + buyText.text = "This Marketplace item isn't an entity. It will not be added to your Purchases."; + } + } + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/images/01cat.jpg b/interface/resources/qml/hifi/commerce/images/01cat.jpg deleted file mode 100644 index 6e7897cb82..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/01cat.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/02car.jpg b/interface/resources/qml/hifi/commerce/images/02car.jpg deleted file mode 100644 index 5dd8091e57..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/02car.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/03dog.jpg b/interface/resources/qml/hifi/commerce/images/03dog.jpg deleted file mode 100644 index 4a85b80c0c..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/03dog.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/04stars.jpg b/interface/resources/qml/hifi/commerce/images/04stars.jpg deleted file mode 100644 index 8f2bf62f83..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/04stars.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/05plane.jpg b/interface/resources/qml/hifi/commerce/images/05plane.jpg deleted file mode 100644 index 6504459d8b..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/05plane.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg deleted file mode 100644 index 54c37faa2f..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml new file mode 100644 index 0000000000..af32f5cfb7 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -0,0 +1,143 @@ +// +// PurchasedItem.qml +// qml/hifi/commerce/purchases +// +// PurchasedItem +// +// Created by Zach Fox on 2017-08-25 +// 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 Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls +import "../wallet" as HifiWallet + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string itemName: ""; + property string itemId: ""; + property string itemPreviewImageUrl: ""; + property string itemHref: ""; + // Style + color: hifi.colors.white; + // Size + width: parent.width; + height: 120; + + Image { + id: itemPreviewImage; + source: root.itemPreviewImageUrl; + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.top: parent.top; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + width: 180; + fillMode: Image.PreserveAspectFit; + + MouseArea { + anchors.fill: parent; + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); + } + } + } + + + RalewayRegular { + id: itemName; + anchors.top: itemPreviewImage.top; + anchors.left: itemPreviewImage.right; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + height: 30; + // Text size + size: 20; + // Style + color: hifi.colors.blueAccent; + text: root.itemName; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); + } + onEntered: { + itemName.color = hifi.colors.blueHighlight; + } + onExited: { + itemName.color = hifi.colors.blueAccent; + } + } + } + + Item { + id: buttonContainer; + anchors.top: itemName.bottom; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.left: itemPreviewImage.right; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + + // "Rez" button + HifiControlsUit.Button { + id: rezButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: parent.height/2 - 4; + text: "Rez Item" + onClicked: { + if (urlHandler.canHandleUrl(root.itemHref)) { + urlHandler.handleUrl(root.itemHref); + } + } + } + + // "More Info" button + HifiControlsUit.Button { + id: moreInfoButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: parent.height/2 - 4; + text: "More Info" + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); + } + } + } + + // + // FUNCTION DEFINITIONS START + // + signal sendToPurchases(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml new file mode 100644 index 0000000000..5060b65e0b --- /dev/null +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -0,0 +1,414 @@ +// +// Purchases.qml +// qml/hifi/commerce/purchases +// +// Purchases +// +// Created by Zach Fox on 2017-08-25 +// 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 Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit +import "../../../controls" as HifiControls +import "../wallet" as HifiWallet + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string activeView: "initialize"; + property string referrerURL: ""; + property bool securityImageResultReceived: false; + property bool keyFilePathIfExistsResultReceived: false; + property bool punctuationMode: false; + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + + onSecurityImageResult: { + securityImageResultReceived = true; + if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { + root.activeView = "purchasesMain"; + } else if (exists) { + // just set the source again (to be sure the change was noticed) + securityImage.source = ""; + securityImage.source = "image://security/securityImage"; + } + } + + onKeyFilePathIfExistsResult: { + keyFilePathIfExistsResultReceived = true; + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + root.activeView = "purchasesMain"; + } + } + + onInventoryResult: { + if (result.status !== 'success') { + console.log("Failed to get purchases", result.message); + } else { + purchasesModel.append(result.data.assets); + filteredPurchasesModel.append(result.data.assets); + } + } + } + + HifiWallet.SecurityImageModel { + id: securityImageModel; + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + height: 50; + // Anchors + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "PURCHASES"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Security Image (TEMPORARY!) + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height - 10; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + source: "image://security/securityImage"; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + Rectangle { + id: initialize; + visible: root.activeView === "initialize"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.baseGray; + + Component.onCompleted: { + commerce.getSecurityImage(); + commerce.getKeyFilePathIfExists(); + } + } + + // + // "WALLET NOT SET UP" START + // + Item { + id: notSetUp; + visible: root.activeView === "notSetUp"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: notSetUpText; + text: "Your wallet isn't set up.

Set up your Wallet (no credit card necessary) to claim your free HFC " + + "and get items from the Marketplace."; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.bottom: notSetUpActionButtonsContainer.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: notSetUpActionButtonsContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 24; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelButton; + 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: "Cancel" + onClicked: { + sendToScript({method: 'purchases_backClicked', referrerURL: referrerURL}); + } + } + + // "Set Up" button + HifiControlsUit.Button { + id: setUpButton; + 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: "Set Up Wallet" + onClicked: { + sendToScript({method: 'checkout_setUpClicked'}); + } + } + } + } + // + // "WALLET NOT SET UP" END + // + + // + // PURCHASES CONTENTS START + // + Item { + id: purchasesContentsContainer; + visible: root.activeView === "purchasesMain"; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 4; + anchors.right: parent.right; + anchors.rightMargin: 4; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: actionButtonsContainer.top; + anchors.bottomMargin: 8; + + onVisibleChanged: { + if (visible) { + commerce.balance(); + commerce.inventory(); + } + } + + // + // FILTER BAR START + // + Item { + id: filterBarContainer; + // Size + height: 40; + // Anchors + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.topMargin: 4; + + HifiControlsUit.TextField { + id: filterBar; + property int previousLength: 0; + anchors.fill: parent; + placeholderText: "Filter"; + + onTextChanged: { + if (filterBar.text.length < previousLength) { + filteredPurchasesModel.clear(); + + for (var i = 0; i < purchasesModel.count; i++) { + filteredPurchasesModel.append(purchasesModel.get(i)); + } + } + + for (var i = 0; i < filteredPurchasesModel.count; i++) { + if (filteredPurchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) === -1) { + filteredPurchasesModel.remove(i); + i--; + } + } + previousLength = filterBar.text.length; + } + + onAccepted: { + focus = false; + } + } + } + // + // FILTER BAR END + // + + ListModel { + id: purchasesModel; + } + ListModel { + id: filteredPurchasesModel; + } + + ListView { + id: purchasesContentsList; + clip: true; + model: filteredPurchasesModel; + // Anchors + anchors.top: filterBarContainer.bottom; + anchors.topMargin: 12; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: parent.width; + delegate: PurchasedItem { + itemName: title; + itemId: id; + itemPreviewImageUrl: preview; + itemHref: root_file_url; + anchors.topMargin: 12; + anchors.bottomMargin: 12; + + Connections { + onSendToPurchases: { + if (msg.method === 'purchases_itemInfoClicked') { + sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId}); + } + } + } + } + } + } + // + // PURCHASES CONTENTS END + // + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + visible: purchasesContentsContainer.visible; + // Size + width: parent.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: keyboard.top; + anchors.bottomMargin: 8; + + // "Back" button + HifiControlsUit.Button { + id: backButton; + 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: "Back" + onClicked: { + sendToScript({method: 'purchases_backClicked', referrerURL: referrerURL}); + } + } + } + // + // ACTION BUTTONS END + // + + HifiControlsUit.Keyboard { + id: keyboard; + raised: HMD.mounted && filterBar.focus; + numeric: parent.punctuationMode; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + } + + // + // 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; + default: + console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 88f939d393..33faacd0ab 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -39,6 +39,13 @@ Item { onBalanceResult : { balanceText.text = parseFloat(result.data.balance/100).toFixed(2); } + + onHistoryResult : { + if (result.status === 'success') { + var txt = result.data.history.map(function (h) { return h.text; }).join("
"); + transactionHistoryText.text = txt; + } + } } SecurityImageModel { @@ -105,6 +112,7 @@ Item { onVisibleChanged: { if (visible) { commerce.balance(); + commerce.history(); } } } @@ -227,17 +235,14 @@ Item { anchors.right: parent.right; // some placeholder stuff - RalewayRegular { - text: homeMessage.visible ? "you CANNOT scroll through this." : "you CAN scroll through this"; - // Text size - size: 16; - // Anchors + TextArea { + id: transactionHistoryText; + text: "
history unavailable
"; + textFormat: TextEdit.AutoText; + font.pointSize: 10; anchors.fill: parent; - // Style - color: hifi.colors.darkGray; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; } } @@ -351,6 +356,7 @@ Item { } } signal sendSignalToWallet(var msg); + // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 25f672e7a9..71e59e0d01 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -26,24 +26,48 @@ Item { visible: source.visible width: parent.width - CheckBox { + Item { id: check - // FIXME: Should use radio buttons if source.exclusiveGroup. + anchors { left: parent.left leftMargin: hifi.dimensions.menuPadding.x + 15 verticalCenter: label.verticalCenter } - width: 20 - visible: source.visible && source.type === 1 && source.checkable - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; + + width: checkbox.visible ? checkbox.width : radiobutton.width + height: checkbox.visible ? checkbox.height : radiobutton.height + + CheckBox { + id: checkbox + // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 + visible: source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup + checked: setChecked() + function setChecked() { + if (!source || source.type !== 1 || !source.checkable) { + return false; + } + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + return source.checked; + } + } + + RadioButton { + id: radiobutton + // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 + visible: source.visible && source.type === 1 && source.checkable && source.exclusiveGroup + checked: setChecked() + function setChecked() { + if (!source || source.type !== 1 || !source.checkable) { + return false; + } + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + return source.checked; } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 9076cd6c48..e7eefbc5e7 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -64,8 +64,10 @@ Item { d.pop(); } - function toModel(items) { + function toModel(items, newMenu) { var result = modelMaker.createObject(tabletMenu); + var exclusionGroups = {}; + for (var i = 0; i < items.length; ++i) { var item = items[i]; if (!item.visible) continue; @@ -77,6 +79,28 @@ Item { if (item.text !== "Users Online") { result.append({"name": item.text, "item": item}) } + + for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j) + { + var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j); + if(entry.menuItem == item.toString()) + { + var exclusionGroupId = entry.exclusionGroup; + console.debug('item exclusionGroupId: ', exclusionGroupId) + + if(!exclusionGroups[exclusionGroupId]) + { + exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu); + console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId]) + } + + var exclusionGroup = exclusionGroups[exclusionGroupId]; + + item.exclusiveGroup = exclusionGroup + console.debug('item.exclusiveGroup: ', item.exclusiveGroup) + } + } + break; case MenuItemType.Separator: result.append({"name": "", "item": item}) @@ -133,10 +157,21 @@ Item { } } + property Component exclusiveGroupMaker: Component { + ExclusiveGroup { + } + } + function buildMenu(items) { - var model = toModel(items); // Menus must be childed to desktop for Z-ordering - var newMenu = menuViewMaker.createObject(tabletMenu, { model: model, isSubMenu: topMenu !== null }); + var newMenu = menuViewMaker.createObject(tabletMenu); + console.debug('newMenu created: ', newMenu) + + var model = toModel(items, newMenu); + + newMenu.model = model; + newMenu.isSubMenu = topMenu !== null; + pushMenu(newMenu); return newMenu; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cf30fc5002..d9cbdd9b06 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1026,6 +1026,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); + if (cmdOptionExists(argc, constArgv, "--system-cursor")) { + _preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM)); + } showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get())); // enable mouse tracking; otherwise, we only get drag events @@ -4564,10 +4567,13 @@ void Application::updateMyAvatarLookAtPosition() { } } else { AvatarSharedPointer lookingAt = myAvatar->getLookAtTargetAvatar().lock(); - if (lookingAt && myAvatar.get() != lookingAt.get()) { + bool haveLookAtCandidate = lookingAt && myAvatar.get() != lookingAt.get(); + auto avatar = static_pointer_cast(lookingAt); + bool mutualLookAtSnappingEnabled = avatar && avatar->getLookAtSnappingEnabled() && myAvatar->getLookAtSnappingEnabled(); + if (haveLookAtCandidate && mutualLookAtSnappingEnabled) { // If I am looking at someone else, look directly at one of their eyes isLookingAtSomeone = true; - auto lookingAtHead = static_pointer_cast(lookingAt)->getHead(); + auto lookingAtHead = avatar->getHead(); const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2c4a515736..005d478411 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -56,6 +56,8 @@ Menu* Menu::getInstance() { return dynamic_cast(qApp->getWindow()->menuBar()); } +const char* exclusionGroupKey = "exclusionGroup"; + Menu::Menu() { auto dialogsManager = DependencyManager::get(); auto accountManager = DependencyManager::get(); @@ -222,32 +224,42 @@ Menu::Menu() { cameraModeGroup->setExclusive(true); // View > First Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F, true, qApp, SLOT(cameraMenuChanged()))); + firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Third Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G, false, qApp, SLOT(cameraMenuChanged()))); + thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Mirror - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H, false, qApp, SLOT(cameraMenuChanged()))); + viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Independent [advanced] - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::IndependentMode, 0, false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); + viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Entity Camera [advanced] - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CameraEntityMode, 0, false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); + viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + viewMenu->addSeparator(); // View > Center Player In View @@ -532,6 +544,11 @@ Menu::Menu() { action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true); + connect(action, &QAction::triggered, [this, avatar]{ + avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping)); + }); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4e21cfa4ac..d77c87a6fc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -177,6 +177,7 @@ namespace MenuOption { const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowMyLookAtVectors = "Show My Eye Vectors"; const QString ShowOtherLookAtVectors = "Show Other Eye Vectors"; + const QString EnableLookAtSnapping = "Enable LookAt Snapping"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; const QString SimulateEyeTracking = "Simulate"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4b0186d583..91af85a108 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -913,6 +913,9 @@ void MyAvatar::saveData() { settings.setValue("scale", _targetScale); + settings.setValue("yawSpeed", _yawSpeed); + settings.setValue("pitchSpeed", _pitchSpeed); + // only save the fullAvatarURL if it has not been overwritten on command line // (so the overrideURL is not valid), or it was overridden _and_ we specified // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) @@ -1063,6 +1066,9 @@ void MyAvatar::loadData() { getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); + _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed); + _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed); + _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString())); _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); @@ -1186,6 +1192,15 @@ int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) { return buffer.size(); } +ScriptAvatarData* MyAvatar::getTargetAvatar() const { + auto avatar = std::static_pointer_cast(_lookAtTargetAvatar.lock()); + if (avatar) { + return new ScriptAvatar(avatar); + } else { + return nullptr; + } +} + void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head @@ -1214,9 +1229,8 @@ void MyAvatar::updateLookAtTargetAvatar() { if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) { _lookAtTargetAvatar = avatarPointer; _targetAvatarPosition = avatarPointer->getPosition(); - smallestAngleTo = angleTo; } - if (isLookingAtMe(avatar)) { + if (_lookAtSnappingEnabled && avatar->getLookAtSnappingEnabled() && isLookingAtMe(avatar)) { // Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face. glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar. @@ -1233,14 +1247,19 @@ void MyAvatar::updateLookAtTargetAvatar() { ViewFrustum viewFrustum; qApp->copyViewFrustum(viewFrustum); + glm::vec3 viewPosition = viewFrustum.getPosition(); +#if DEBUG_ALWAYS_LOOKAT_EYES_NOT_CAMERA + viewPosition = (avatarLeftEye + avatarRightEye) / 2.0f; +#endif // scale gazeOffset by IPD, if wearing an HMD. if (qApp->isHMDMode()) { + glm::quat viewOrientation = viewFrustum.getOrientation(); glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left); glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right); glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); - glm::vec3 humanLeftEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * leftEyeHeadLocal); - glm::vec3 humanRightEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * rightEyeHeadLocal); + glm::vec3 humanLeftEye = viewPosition + (viewOrientation * leftEyeHeadLocal); + glm::vec3 humanRightEye = viewPosition + (viewOrientation * rightEyeHeadLocal); auto hmdInterface = DependencyManager::get(); float ipdScale = hmdInterface->getIPDScale(); @@ -1254,7 +1273,7 @@ void MyAvatar::updateLookAtTargetAvatar() { } // And now we can finally add that offset to the camera. - glm::vec3 corrected = viewFrustum.getPosition() + gazeOffset; + glm::vec3 corrected = viewPosition + gazeOffset; avatar->getHead()->setCorrectedLookAtPosition(corrected); @@ -2464,6 +2483,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); + setProperty("lookAtSnappingEnabled", menu->isOptionChecked(MenuOption::EnableLookAtSnapping)); } void MyAvatar::setFlyingEnabled(bool enabled) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b7f004774d..1c7cd559a6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -22,8 +22,9 @@ #include #include -#include #include +#include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" @@ -141,6 +142,9 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) + Q_PROPERTY(float yawSpeed MEMBER _yawSpeed) + Q_PROPERTY(float pitchSpeed MEMBER _pitchSpeed) + Q_PROPERTY(bool hmdRollControlEnabled READ getHMDRollControlEnabled WRITE setHMDRollControlEnabled) Q_PROPERTY(float hmdRollControlDeadZone READ getHMDRollControlDeadZone WRITE setHMDRollControlDeadZone) Q_PROPERTY(float hmdRollControlRate READ getHMDRollControlRate WRITE setHMDRollControlRate) @@ -396,6 +400,7 @@ public: Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); } Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } + Q_INVOKABLE ScriptAvatarData* getTargetAvatar() const; Q_INVOKABLE glm::vec3 getLeftHandPosition() const; Q_INVOKABLE glm::vec3 getRightHandPosition() const; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 55ee0e16e9..dddfae6455 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -45,6 +45,7 @@ Handler(buy) Handler(receiveAt) Handler(balance) Handler(inventory) +Handler(history) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -107,3 +108,6 @@ void Ledger::inventory(const QStringList& keys) { keysQuery("inventory", "inventorySuccess", "inventoryFailure"); } +void Ledger::history(const QStringList& keys) { + keysQuery("history", "historySuccess", "historyFailure"); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index e43b8453b9..7d3fdef0c0 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,12 +28,14 @@ public: bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); + void history(const QStringList& keys); signals: void buyResult(QJsonObject result); void receiveAtResult(QJsonObject result); void balanceResult(QJsonObject result); void inventoryResult(QJsonObject result); + void historyResult(QJsonObject result); public slots: void buySuccess(QNetworkReply& reply); @@ -44,6 +46,8 @@ public slots: void balanceFailure(QNetworkReply& reply); void inventorySuccess(QNetworkReply& reply); void inventoryFailure(QNetworkReply& reply); + void historySuccess(QNetworkReply& reply); + void historyFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index a5ae008a36..bf6bcc221c 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -25,6 +25,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult); + connect(ledger.data(), &Ledger::historyResult, this, &QmlCommerce::historyResult); connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); } @@ -46,26 +47,37 @@ void QmlCommerce::balance() { auto wallet = DependencyManager::get(); ledger->balance(wallet->listPublicKeys()); } + void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); ledger->inventory(wallet->listPublicKeys()); } +void QmlCommerce::history() { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + ledger->history(wallet->listPublicKeys()); +} + void QmlCommerce::chooseSecurityImage(const QString& imageFile) { auto wallet = DependencyManager::get(); wallet->chooseSecurityImage(imageFile); } + void QmlCommerce::getSecurityImage() { auto wallet = DependencyManager::get(); wallet->getSecurityImage(); } + void QmlCommerce::getLoginStatus() { emit loginStatusResult(DependencyManager::get()->isLoggedIn()); } + void QmlCommerce::setPassphrase(const QString& passphrase) { emit passphraseSetupStatusResult(true); } + void QmlCommerce::getPassphraseSetupStatus() { emit passphraseSetupStatusResult(false); } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 0c3ed64c81..fd913ae4b7 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -36,12 +36,14 @@ signals: void securityImageResult(bool exists); void loginStatusResult(bool isLoggedIn); void passphraseSetupStatusResult(bool passphraseIsSetup); + void historyResult(QJsonObject result); void keyFilePathIfExistsResult(const QString& path); protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); + Q_INVOKABLE void history(); Q_INVOKABLE void chooseSecurityImage(const QString& imageFile); Q_INVOKABLE void getSecurityImage(); Q_INVOKABLE void getLoginStatus(); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 1d64733f49..9e04694a24 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -347,7 +347,7 @@ bool Wallet::createIfNeeded() { qCDebug(commerce) << "read private key"; RSA_free(key); // K -- add the public key since we have a legit private key associated with it - _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64())); + _publicKeys.push_back(publicKey.toBase64()); return false; } } @@ -419,7 +419,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { } // temporary... QString path = qApp->applicationDirPath(); - path.append("/resources/qml/hifi/commerce/"); + path.append("/resources/qml/hifi/commerce/wallet/"); path.append(filename); // now create a new security image pixmap _securityImage = new QPixmap(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index ede5fd7ac8..fc0ee1df7b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -558,8 +558,8 @@ void Avatar::postUpdate(float deltaTime) { if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; - const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); - const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 BLUE(0.0f, 0.0f, _lookAtSnappingEnabled ? 1.0f : 0.25f, 1.0f); + const glm::vec4 RED(_lookAtSnappingEnabled ? 1.0f : 0.25f, 0.0f, 0.0f, 1.0f); int leftEyeJoint = getJointIndex("LeftEye"); glm::vec3 leftEyePosition; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ed818a596d..16245601dd 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -91,6 +91,7 @@ AvatarData::AvatarData() : _targetVelocity(0.0f), _density(DEFAULT_AVATAR_DENSITY) { + connect(this, &AvatarData::lookAtSnappingChanged, this, &AvatarData::markIdentityDataChanged); } AvatarData::~AvatarData() { @@ -1446,7 +1447,9 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide >> identity.displayName >> identity.sessionDisplayName >> identity.isReplicated - >> identity.avatarEntityData; + >> identity.avatarEntityData + >> identity.lookAtSnappingEnabled + ; // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; @@ -1488,6 +1491,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide identityChanged = true; } + if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) { + setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled); + identityChanged = true; + } + #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ << "identity.uuid:" << identity.uuid @@ -1519,7 +1527,9 @@ QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName << (_isReplicated || setIsReplicated) - << _avatarEntityData; + << _avatarEntityData + << _lookAtSnappingEnabled + ; }); return identityData; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f7955ba5d9..664ef64bde 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -353,6 +353,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale) + Q_PROPERTY(float density READ getDensity) Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition) Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw) Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch) @@ -374,6 +375,7 @@ class AvatarData : public QObject, public SpatiallyNestable { // sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients. // The result is unique among all avatars present at the time. Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName) + Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) @@ -561,6 +563,7 @@ public: QString sessionDisplayName; bool isReplicated; AvatarEntityMap avatarEntityData; + bool lookAtSnappingEnabled; }; // identityChanged returns true if identity has changed, false otherwise. @@ -573,6 +576,7 @@ public: const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } const QString& getSessionDisplayName() const { return _sessionDisplayName; } + bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; } virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); @@ -665,6 +669,7 @@ public: signals: void displayNameChanged(); + void lookAtSnappingChanged(bool enabled); public slots: void sendAvatarDataPacket(); @@ -733,6 +738,7 @@ protected: QVector _attachmentData; QString _displayName; QString _sessionDisplayName { }; + bool _lookAtSnappingEnabled { true }; QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys QStringList _fstJointNames; ///< in order of depth-first traversal diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 90ec7ec309..d0643d28f8 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -15,6 +15,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : _avatarData(avatarData) { QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged); + QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged); } // @@ -161,6 +162,13 @@ bool ScriptAvatarData::getIsReplicated() const { } } +bool ScriptAvatarData::getLookAtSnappingEnabled() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getLookAtSnappingEnabled(); + } else { + return false; + } +} // // IDENTIFIER PROPERTIES // END diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 1b6944e01d..46dfb5325f 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -46,6 +46,7 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) Q_PROPERTY(bool isReplicated READ getIsReplicated) + Q_PROPERTY(bool lookAtSnappingEnabled READ getLookAtSnappingEnabled NOTIFY lookAtSnappingChanged) // // ATTACHMENT AND JOINT PROPERTIES @@ -97,6 +98,7 @@ public: QString getDisplayName() const; QString getSessionDisplayName() const; bool getIsReplicated() const; + bool getLookAtSnappingEnabled() const; // // ATTACHMENT AND JOINT PROPERTIES @@ -129,6 +131,7 @@ public: signals: void displayNameChanged(); + void lookAtSnappingChanged(bool enabled); public slots: glm::quat getAbsoluteJointRotationInObjectFrame(int index) const; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 4dc8d3378c..f09b3d7109 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -437,7 +437,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const mousePosition -= 1.0; mousePosition.y *= -1.0f; - vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; + vec2 mouseSize = CURSOR_PIXEL_SIZE * Cursor::Manager::instance().getScale() / canvasSize; result = glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f)); } return result; @@ -451,3 +451,13 @@ QVariant ReticleInterface::getPosition() const { void ReticleInterface::setPosition(QVariant position) { _compositor->setReticlePosition(vec2FromVariant(position)); } + +float ReticleInterface::getScale() const { + auto& cursorManager = Cursor::Manager::instance(); + return cursorManager.getScale(); +} + +void ReticleInterface::setScale(float scale) { + auto& cursorManager = Cursor::Manager::instance(); + cursorManager.setScale(scale); +} diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index e6a32dcfb9..946229fc9b 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -176,6 +176,7 @@ class ReticleInterface : public QObject { Q_PROPERTY(QVariant position READ getPosition WRITE setPosition) Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) + Q_PROPERTY(float scale READ getScale WRITE setScale) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) @@ -197,6 +198,9 @@ public: Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } + Q_INVOKABLE float getScale() const; + Q_INVOKABLE void setScale(float scale); + Q_INVOKABLE QVariant getPosition() const; Q_INVOKABLE void setPosition(QVariant position); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 113739d99b..9f4a9a3c4a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -47,6 +47,11 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _displayModelBounds(false), _layeredZones(this) { + setMouseRayPickResultOperator([](QUuid rayPickID) { + RayToEntityIntersectionResult entityResult; + return entityResult; + }); + setSetPrecisionPickingOperator([](QUuid rayPickID, bool value) {}); EntityRenderer::initEntityRenderers(); _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 241ccaf5d6..f44beb6d74 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -37,7 +37,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity); + return static_cast(AvatarMixerPacketVersion::AvatarIdentityLookAtSnapping); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3314e69d78..e8b74a74d5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -288,7 +288,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentitySequenceId, MannequinDefaultAvatar, AvatarIdentitySequenceFront, - IsReplicatedInAvatarIdentity + IsReplicatedInAvatarIdentity, + AvatarIdentityLookAtSnapping, }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 3959e950e9..70daff944a 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -26,12 +26,14 @@ static unsigned int USER_DATA_ID = 0; // and qml object and inject the pointer into both objects class MenuUserData : public QObjectUserData { public: - MenuUserData(QAction* action, QObject* qmlObject) { + MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent) { if (!USER_DATA_ID) { USER_DATA_ID = DependencyManager::get()->getMenuUserDataId(); } _action = action; _qml = qmlObject; + _qmlParent = qmlParent; + action->setUserData(USER_DATA_ID, this); qmlObject->setUserData(USER_DATA_ID, this); qmlObject->setObjectName(uuid.toString()); @@ -43,6 +45,41 @@ public: _shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] { QObject::disconnect(_changedConnection); }); + + class ExclusionGroupSetter : public QObject { + public: + ExclusionGroupSetter(QObject* from, QObject* to, QObject* qmlParent) : QObject(from), _from(from), _to(to), _qmlParent(qmlParent) { + _from->installEventFilter(this); + } + + ~ExclusionGroupSetter() { + _from->removeEventFilter(this); + } + protected: + virtual bool eventFilter(QObject* o, QEvent* e) override { + if (e->type() == QEvent::DynamicPropertyChange) { + QDynamicPropertyChangeEvent* dpc = static_cast(e); + if (dpc->propertyName() == "exclusionGroup") + { + // unfortunately Qt doesn't support passing dynamic properties between C++ / QML, so we have to use this ugly helper function + QMetaObject::invokeMethod(_qmlParent, + "addExclusionGroup", + Qt::DirectConnection, + Q_ARG(QVariant, QVariant::fromValue(_to)), + Q_ARG(QVariant, _from->property(dpc->propertyName()))); + } + } + + return QObject::eventFilter(o, e); + } + + private: + QObject* _from; + QObject* _to; + QObject* _qmlParent; + }; + + new ExclusionGroupSetter(action, qmlObject, qmlParent); } ~MenuUserData() { @@ -110,6 +147,7 @@ private: QMetaObject::Connection _changedConnection; QAction* _action { nullptr }; QObject* _qml { nullptr }; + QObject* _qmlParent{ nullptr }; }; @@ -157,16 +195,16 @@ void VrMenu::addMenu(QMenu* menu) { } // Bind the QML and Widget together - new MenuUserData(menu->menuAction(), result); + new MenuUserData(menu->menuAction(), result, qmlParent); } -void bindActionToQmlAction(QObject* qmlAction, QAction* action) { +void bindActionToQmlAction(QObject* qmlAction, QAction* action, QObject* qmlParent) { auto text = action->text(); if (text == "Login") { qDebug(uiLogging) << "Login action " << action; } - new MenuUserData(action, qmlAction); + new MenuUserData(action, qmlAction, qmlParent); QObject::connect(action, &QAction::toggled, [=](bool checked) { qmlAction->setProperty("checked", checked); }); @@ -195,7 +233,7 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); // Bind the QML and Widget together - bindActionToQmlAction(result, action); + bindActionToQmlAction(result, action, _rootMenu); } void VrMenu::addSeparator(QMenu* menu) { @@ -231,7 +269,7 @@ void VrMenu::insertAction(QAction* before, QAction* action) { Q_ASSERT(invokeResult); QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); - bindActionToQmlAction(result, action); + bindActionToQmlAction(result, action, _rootMenu); } class QQuickMenuBase; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 4b758f0add..7f24e7f634 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -14,7 +14,7 @@ /*global XXX */ (function () { // BEGIN LOCAL_SCOPE - + Script.include("/~/system/libraries/accountUtils.js"); // Function Name: onButtonClicked() // @@ -59,13 +59,7 @@ tablet.gotoHomeScreen(); break; case 'walletSetup_loginClicked': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLOnTop("../../../dialogs/TabletLoginDialog.qml"); - } + openLoginWindow(); break; default: print('Unrecognized message from QML:', JSON.stringify(message)); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 2889a1514a..62a4dcf369 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -90,20 +90,20 @@ }); } - function addInventoryButton() { + function addPurchasesButton() { // Why isn't this an id?! This really shouldn't be a class on the website, but it is. var navbarBrandElement = document.getElementsByClassName('navbar-brand')[0]; - var inventoryElement = document.createElement('a'); - inventoryElement.classList.add("btn"); - inventoryElement.classList.add("btn-default"); - inventoryElement.id = "inventoryButton"; - inventoryElement.setAttribute('href', "#"); - inventoryElement.innerHTML = "INVENTORY"; - inventoryElement.style = "height:100%;margin-top:0;padding:15px 15px;"; - navbarBrandElement.parentNode.insertAdjacentElement('beforeend', inventoryElement); - $('#inventoryButton').on('click', function () { + var purchasesElement = document.createElement('a'); + purchasesElement.classList.add("btn"); + purchasesElement.classList.add("btn-default"); + purchasesElement.id = "purchasesButton"; + purchasesElement.setAttribute('href', "#"); + purchasesElement.innerHTML = "PURCHASES"; + purchasesElement.style = "height:100%;margin-top:0;padding:15px 15px;"; + navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); + $('#purchasesButton').on('click', function () { EventBridge.emitWebEvent(JSON.stringify({ - type: "INVENTORY", + type: "PURCHASES", referrerURL: window.location.href })); }); @@ -121,11 +121,19 @@ } function injectBuyButtonOnMainPage() { + var cost; + $('.grid-item').find('#price-or-edit').find('a').each(function() { $(this).attr('data-href', $(this).attr('href')); $(this).attr('href', '#'); - }); - $('.grid-item').find('#price-or-edit').find('.price').text("BUY"); + cost = $(this).closest('.col-xs-3').find('.item-cost').text(); + + if (parseInt(cost) > 0) { + $(this).find('.price').text("BUY"); + } + }); + + $('.grid-item').find('#price-or-edit').find('a').on('click', function () { buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), $(this).closest('.grid-item').find('.item-title').text(), @@ -153,7 +161,7 @@ // Try this here in case it works (it will if the user just pressed the "back" button, // since that doesn't trigger another AJAX request. injectBuyButtonOnMainPage; - addInventoryButton(); + addPurchasesButton(); } } @@ -161,15 +169,21 @@ if (confirmAllPurchases) { var href = $('#side-info').find('.btn').attr('href'); $('#side-info').find('.btn').attr('href', '#'); - $('#side-info').find('.btn').html('Buy Item '); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0) { + $('#side-info').find('.btn').html('Buy Item '); + } + $('#side-info').find('.btn').on('click', function () { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), - $('.item-cost').text(), + cost, href); }); - addInventoryButton(); + addPurchasesButton(); } } diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js new file mode 100644 index 0000000000..6df0aa3a87 --- /dev/null +++ b/scripts/system/libraries/accountUtils.js @@ -0,0 +1,16 @@ +// +// accountUtils.js +// scripts/system/libraries/libraries +// +// Copyright 2017 High Fidelity, Inc. +// + +openLoginWindow = function openLoginWindow() { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); + } +}; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 84d7d44689..7b479cdcad 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,9 +19,9 @@ 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.qml"; - var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml"; - var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml"; + var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/checkout/Checkout.qml"; + 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 HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -88,7 +88,7 @@ function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL; - wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH)); + wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_PURCHASES_QML_PATH)); // for toolbar mode: change button to active when window is first openend, false otherwise. marketplaceButton.editProperties({ isActive: onMarketplaceScreen }); if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { @@ -141,10 +141,10 @@ action: "inspectionModeSetting", data: Settings.getValue("inspectionMode", false) })); - } else if (parsedJsonMessage.type === "INVENTORY") { - tablet.pushOntoStack(MARKETPLACE_INVENTORY_QML_PATH); + } else if (parsedJsonMessage.type === "PURCHASES") { + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); tablet.sendToQml({ - method: 'updateInventory', + method: 'updatePurchases', referrerURL: parsedJsonMessage.referrerURL }); } @@ -199,30 +199,37 @@ // in the format "{method, params}", like json-rpc. function fromQml(message) { switch (message.method) { + case 'checkout_setUpClicked': + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + break; case 'checkout_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack. // I don't think this is trivial to do since we also want to inject some JS into the DOM. //tablet.popFromStack(); break; - case 'checkout_buySuccess': + case 'checkout_goToPurchases': + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + tablet.sendToQml({ + method: 'updatePurchases', + referrerURL: MARKETPLACE_URL_INITIAL + }); + break; + case 'checkout_continueShopping': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); //tablet.popFromStack(); break; - case 'inventory_itemClicked': + case 'purchases_itemInfoClicked': var itemId = message.itemId; if (itemId && itemId !== "") { tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); } break; - case 'inventory_backClicked': - tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); - break; - case 'securityImageSelection_cancelClicked': + case 'purchases_backClicked': tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); break; default: - print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message)); + print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index df5ed45fed..e5c60af77b 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -10,7 +10,8 @@ /* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; @@ -52,15 +53,7 @@ try { print('Failed to resolve request api, error: ' + err); } -function openLoginWindow() { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } -} + function removeFromStoryIDsToMaybeDelete(story_id) { storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);