Merge branch 'master' into 21484

This commit is contained in:
Brad Davis 2017-08-31 14:59:30 -07:00 committed by GitHub
commit 65b49a958f
262 changed files with 14797 additions and 8083 deletions

View file

@ -3,7 +3,7 @@ This is a stand-alone guide for creating your first High Fidelity build for Wind
## Building High Fidelity
Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide.
Note: The prerequisites will require about 10 GB of space on your drive.
Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.
### Step 1. Visual Studio 2017

View file

@ -247,6 +247,9 @@ endif()
set_packaging_parameters()
option(BUILD_TESTS "Build tests" ON)
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
# add subdirectories for all targets
if (NOT ANDROID)
add_subdirectory(assignment-client)
@ -259,7 +262,9 @@ if (NOT ANDROID)
if (NOT SERVER_ONLY)
add_subdirectory(interface)
set_target_properties(interface PROPERTIES FOLDER "Apps")
add_subdirectory(tests)
if (BUILD_TESTS)
add_subdirectory(tests)
endif()
endif()
add_subdirectory(plugins)
add_subdirectory(tools)

View file

@ -54,6 +54,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
if (message->getSize() == 0) {
return;
}
QDataStream packetStream(message->getMessage());
// read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it

View file

@ -458,7 +458,7 @@ const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled";
bool DomainServer::packetVersionMatch(const udt::Packet& packet) {
bool DomainServer::isPacketVerified(const udt::Packet& packet) {
PacketType headerType = NLPacket::typeInHeader(packet);
PacketVersion headerVersion = NLPacket::versionInHeader(packet);
@ -471,7 +471,48 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) {
DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr());
}
// let the normal nodeList implementation handle all other packets.
if (!PacketTypeEnum::getNonSourcedPackets().contains(headerType)) {
// this is a sourced packet - first check if we have a node that matches
QUuid sourceID = NLPacket::sourceIDInHeader(packet);
SharedNodePointer sourceNode = nodeList->nodeWithUUID(sourceID);
if (sourceNode) {
// unverified DS packets (due to a lack of connection secret between DS + node)
// must come either from the same public IP address or a local IP address (set by RFC 1918)
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sourceNode->getLinkedData());
bool exactAddressMatch = nodeData->getSendingSockAddr() == packet.getSenderSockAddr();
bool bothPrivateAddresses = nodeData->getSendingSockAddr().hasPrivateAddress()
&& packet.getSenderSockAddr().hasPrivateAddress();
if (nodeData && (exactAddressMatch || bothPrivateAddresses)) {
// to the best of our ability we've verified that this packet comes from the right place
// let the NodeList do its checks now (but pass it the sourceNode so it doesn't need to look it up again)
return nodeList->isPacketVerifiedWithSource(packet, sourceNode.data());
} else {
static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unmatched IP for UUID";
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX);
qDebug() << "Packet of type" << headerType
<< "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceID);
return false;
}
} else {
static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID";
static QString repeatedMessage
= LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX);
qDebug() << "Packet of type" << headerType
<< "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID);
return false;
}
}
// fallback to allow the normal NodeList implementation to verify packets
return nodeList->isPacketVerified(packet);
}
@ -570,7 +611,7 @@ void DomainServer::setupNodeListAndAssignments() {
addStaticAssignmentsToQueue();
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch);
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
}
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
@ -853,7 +894,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
}
void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
QDataStream packetStream(message->getMessage());
NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false);

View file

@ -71,6 +71,7 @@ public slots:
void restart();
private slots:
void processRequestAssignmentPacket(QSharedPointer<ReceivedMessage> packet);
void processListRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
@ -79,7 +80,6 @@ public slots:
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
private slots:
void setupPendingAssignmentCredits();
void sendPendingTransactionsToServer();
@ -129,7 +129,7 @@ private:
void getTemporaryName(bool force = false);
static bool packetVersionMatch(const udt::Packet& packet);
static bool isPacketVerified(const udt::Packet& packet);
bool resetAccountManagerAccessToken();

View file

@ -18,6 +18,7 @@ find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5LinguistToolsMacros)
if (WIN32)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -bigobj")
add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h
add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines
endif()

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -39,7 +39,7 @@ Item {
width: parent.width
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
profile: HFTabletWebEngineProfile;
profile: HFWebEngineProfile;
property string userScriptUrl: ""

View file

@ -31,7 +31,7 @@ Item {
width: parent.width
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
profile: HFTabletWebEngineProfile;
profile: HFWebEngineProfile;
property string userScriptUrl: ""

View file

@ -24,6 +24,7 @@ Item {
property alias webView: webview
property alias profile: webview.profile
property bool remove: false
property bool closeButtonVisible: true
// Manage own browse history because WebEngineView history is wiped when a new URL is loaded via
// onNewViewRequested, e.g., as happens when a social media share button is clicked.
@ -64,6 +65,7 @@ Item {
disabledColor: hifi.colors.lightGrayText
enabled: true
text: "CLOSE"
visible: closeButtonVisible
MouseArea {
anchors.fill: parent
@ -142,7 +144,7 @@ Item {
width: parent.width
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height - web.headerHeight : parent.height - web.headerHeight
anchors.top: buttons.bottom
profile: HFTabletWebEngineProfile;
profile: HFWebEngineProfile;
property string userScriptUrl: ""
@ -182,8 +184,6 @@ Item {
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
}
onFeaturePermissionRequested: {

View file

@ -73,7 +73,6 @@ Item {
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
});
root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)";
}
onFeaturePermissionRequested: {

View file

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

View file

@ -46,6 +46,8 @@ Item {
property int stackShadowNarrowing: 5;
property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
property int shadowHeight: 10;
property bool hovered: false
HifiConstants { id: hifi }
function pastTime(timestamp) { // Answer a descriptive string
@ -231,6 +233,13 @@ Item {
// to that which is being hovered over.
property var hoverThunk: function () { };
property var unhoverThunk: function () { };
Rectangle {
anchors.fill: parent;
visible: root.hovered
color: "transparent";
border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight;
z: 1;
}
MouseArea {
anchors.fill: parent;
acceptedButtons: Qt.LeftButton;

View file

@ -35,7 +35,6 @@ Column {
property string metaverseServerUrl: '';
property string actions: 'snapshot';
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
Component.onCompleted: delay.start();
property string labelText: actions;
property string filter: '';
onFilterChanged: filterChoicesByText();
@ -125,11 +124,9 @@ Column {
cb();
});
}
property var delay: Timer { // No setTimeout or nextTick in QML.
interval: 0;
onTriggered: fillDestinations();
}
function fillDestinations() { // Public
console.debug('Feed::fillDestinations()')
function report(label, error) {
console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count);
}
@ -206,9 +203,9 @@ Column {
id: scroll;
model: suggestions;
orientation: ListView.Horizontal;
highlightFollowsCurrentItem: false
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight; z: 1; }
currentIndex: -1;
spacing: 12;
@ -237,9 +234,8 @@ Column {
textSizeSmall: root.textSizeSmall;
stackShadowNarrowing: root.stackShadowNarrowing;
shadowHeight: root.stackedCardShadowHeight;
hoverThunk: function () { scrollToIndex(index); }
unhoverThunk: function () { scrollToIndex(-1); }
hoverThunk: function () { hovered = true }
unhoverThunk: function () { hovered = false }
}
}
NumberAnimation {

View file

@ -147,7 +147,7 @@ Rectangle {
width: parent.width;
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
profile: HFTabletWebEngineProfile;
profile: HFWebEngineProfile;
property string userScriptUrl: ""

View file

@ -1,482 +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
// 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;
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onBuyResult: {
if (failureMessage.length) {
buyButton.text = "Buy Failed";
buyButton.enabled = false;
} else {
if (urlHandler.canHandleUrl(itemHref)) {
urlHandler.handleUrl(itemHref);
}
sendToScript({method: 'checkout_buySuccess', itemId: itemId});
}
}
onBalanceResult: {
if (failureMessage.length) {
console.log("Failed to get balance", failureMessage);
} else {
balanceReceived = true;
hfcBalanceText.text = balance;
balanceAfterPurchase = balance - parseInt(itemPriceText.text, 10);
}
}
onInventoryResult: {
if (failureMessage.length) {
console.log("Failed to get inventory", failureMessage);
} else {
inventoryReceived = true;
if (inventoryContains(inventory.assets, itemId)) {
alreadyOwned = true;
} else {
alreadyOwned = false;
}
}
}
onSecurityImageResult: {
securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
}
}
SecurityImageSelection {
id: securityImageSelection;
referrerURL: checkoutRoot.itemHref;
}
//
// TITLE BAR START
//
Item {
id: titleBarContainer;
// Size
width: checkoutRoot.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Security Image
Image {
id: securityImage;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 16;
height: parent.height - 5;
width: height;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
// Title Bar text
RalewaySemiBold {
id: titleBarText;
text: "Checkout";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.top: parent.top;
anchors.left: securityImage.right;
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, parseInt(itemPriceText.text));
} 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;
itemPriceText.text = message.params.itemPrice;
itemHref = message.params.itemHref;
commerce.balance();
commerce.inventory();
commerce.getSecurityImage();
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
//
}

View file

@ -1,321 +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
// 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 (failureMessage.length) {
console.log("Failed to get balance", failureMessage);
} else {
hfcBalanceText.text = balance;
}
}
onInventoryResult: {
if (failureMessage.length) {
console.log("Failed to get inventory", failureMessage);
} else {
inventoryContentsList.model = inventory.assets;
}
}
onSecurityImageResult: {
securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
}
}
SecurityImageSelection {
id: securityImageSelection;
referrerURL: inventoryRoot.referrerURL;
}
//
// TITLE BAR START
//
Item {
id: titleBarContainer;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Security Image
Image {
id: securityImage;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.leftMargin: 16;
height: parent.height - 5;
width: height;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
// Title Bar text
RalewaySemiBold {
id: titleBarText;
text: "Inventory";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.top: parent.top;
anchors.left: securityImage.right;
anchors.leftMargin: 16;
anchors.bottom: parent.bottom;
width: paintedWidth;
// Style
color: hifi.colors.lightGrayText;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// "Change Security Image" button
HifiControlsUit.Button {
id: changeSecurityImageButton;
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: 200;
text: "Change Security Image"
onClicked: {
securityImageSelection.isManuallyChangingSecurityImage = true;
securityImageSelection.visible = true;
}
}
// 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();
commerce.getSecurityImage();
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
}
}
signal sendToScript(var message);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -1,271 +0,0 @@
//
// SecurityImageSelection.qml
// qml/hifi/commerce
//
// SecurityImageSelection
//
// Created by Zach Fox on 2017-08-15
// 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
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: securityImageSelectionRoot;
property string referrerURL: "";
property bool isManuallyChangingSecurityImage: false;
anchors.fill: parent;
// Style
color: hifi.colors.baseGray;
z:999; // On top of everything else
visible: false;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
if (!isManuallyChangingSecurityImage) {
securityImageSelectionRoot.visible = (imageID == 0);
}
if (imageID > 0) {
for (var itr = 0; itr < gridModel.count; itr++) {
var thisValue = gridModel.get(itr).securityImageEnumValue;
if (thisValue === imageID) {
securityImageGrid.currentIndex = itr;
break;
}
}
}
}
}
Component.onCompleted: {
commerce.getSecurityImage();
}
//
// TITLE BAR START
//
Item {
id: titleBarContainer;
// Size
width: securityImageSelectionRoot.width;
height: 30;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
id: titleBarText;
text: "Select a Security Image";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 16;
// 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
//
//
// EXPLANATION START
//
Item {
id: explanationContainer;
// Size
width: securityImageSelectionRoot.width;
height: 85;
// Anchors
anchors.top: titleBarContainer.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
RalewayRegular {
id: explanationText;
text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.<b><br>If you don't see your selected image on these dialogs, do not use them!</b>";
// Text size
size: 16;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 4;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
// Style
color: hifi.colors.lightGrayText;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
}
//
// EXPLANATION END
//
//
// SECURITY IMAGE GRID START
//
Item {
id: securityImageGridContainer;
// Anchors
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
anchors.top: explanationContainer.bottom;
anchors.topMargin: 8;
anchors.bottom: actionButtonsContainer.top;
anchors.bottomMargin: 8;
SecurityImageModel {
id: gridModel;
}
GridView {
id: securityImageGrid;
clip: true;
// Anchors
anchors.fill: parent;
currentIndex: -1;
cellWidth: width / 2;
cellHeight: height / 3;
model: gridModel;
delegate: Item {
width: securityImageGrid.cellWidth;
height: securityImageGrid.cellHeight;
Item {
anchors.fill: parent;
Image {
width: parent.width - 8;
height: parent.height - 8;
source: sourcePath;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
}
MouseArea {
anchors.fill: parent;
onClicked: {
securityImageGrid.currentIndex = index;
}
}
}
highlight: Rectangle {
width: securityImageGrid.cellWidth;
height: securityImageGrid.cellHeight;
color: hifi.colors.blueHighlight;
}
}
}
//
// SECURITY IMAGE GRID END
//
//
// ACTION BUTTONS START
//
Item {
id: actionButtonsContainer;
// Size
width: securityImageSelectionRoot.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: {
if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) {
sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL});
} else {
securityImageSelectionRoot.visible = false;
}
}
}
// "Confirm" button
HifiControlsUit.Button {
id: confirmButton;
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: "Confirm";
onClicked: {
securityImageSelectionRoot.isManuallyChangingSecurityImage = false;
commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue);
}
}
}
//
// ACTION BUTTONS END
//
//
// FUNCTION DEFINITIONS START
//
signal sendToScript(var message);
function getImagePathFromImageID(imageID) {
return (imageID ? gridModel.get(imageID - 1).sourcePath : "");
}
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,944 @@
//
// 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 bool securityImageResultReceived: false;
property bool keyFilePathIfExistsResultReceived: false;
property string itemId: "";
property string itemHref: "";
property double balanceAfterPurchase: 0;
property bool alreadyOwned: false;
property int itemPriceFull: 0;
property bool itemIsJson: true;
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.getSecurityImage();
commerce.getKeyFilePathIfExists();
commerce.balance();
commerce.inventory();
}
}
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:<br><br>" + (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;
visible: !needsLogIn.visible;
// 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";
}
Image {
id: securityImageOverlay;
source: "../wallet/images/lockOverlay.png";
width: securityImage.width * 0.45;
height: securityImage.height * 0.45;
anchors.bottom: securityImage.bottom;
anchors.right: securityImage.right;
mipmap: true;
opacity: 0.9;
}
// 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: {
securityImageResultReceived = false;
purchasesReceived = false;
balanceReceived = false;
keyFilePathIfExistsResultReceived = false;
commerce.getLoginStatus();
}
}
HifiWallet.NeedsLogIn {
id: needsLogIn;
visible: root.activeView === "needsLogIn";
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToWallet: {
sendToScript(msg);
}
}
}
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
}
}
//
// "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: "<b>Your wallet isn't set up.</b><br><br>Set up your Wallet (no credit card necessary) to claim your <b>free HFC</b> " +
"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;
//
// 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: "<b>Item Price:</b>";
// 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: "<b>-- HFC</b>";
// 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: "<b>Purchase Complete!</b><br><br>You bought <b>" + (itemNameText.text) + "</b> by <b>" + (itemAuthorText.text) + "</b>";
// 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: "<b>Purchase Failed.</b><br>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" : "<b>" + (parseFloat(root.itemPriceFull/100).toFixed(2)) + " HFC</b>";
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 <b>Purchases</b> 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 = "<b>You already own this item.</b> 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 <b>Purchases</b>, which can be accessed from <b>Marketplace</b>.";
}
}
} else {
buyText.text = "";
}
} else {
buyText.text = "This Marketplace item isn't an entity. It <b>will not</b> be added to your <b>Purchases</b>.";
}
}
//
// FUNCTION DEFINITIONS END
//
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View file

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

View file

@ -0,0 +1,505 @@
//
// 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 purchasesReceived: false;
property bool punctuationMode: false;
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.getSecurityImage();
commerce.getKeyFilePathIfExists();
commerce.inventory();
}
}
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: {
purchasesReceived = true;
if (result.status !== 'success') {
console.log("Failed to get purchases", result.message);
} else {
purchasesModel.clear();
purchasesModel.append(result.data.assets);
filteredPurchasesModel.clear();
filteredPurchasesModel.append(result.data.assets);
}
}
}
HifiWallet.SecurityImageModel {
id: securityImageModel;
}
//
// TITLE BAR START
//
Item {
id: titleBarContainer;
visible: !needsLogIn.visible;
// 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";
}
Image {
id: securityImageOverlay;
source: "../wallet/images/lockOverlay.png";
width: securityImage.width * 0.45;
height: securityImage.height * 0.45;
anchors.bottom: securityImage.bottom;
anchors.right: securityImage.right;
mipmap: true;
opacity: 0.9;
}
// 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: {
securityImageResultReceived = false;
purchasesReceived = false;
keyFilePathIfExistsResultReceived = false;
commerce.getLoginStatus();
}
}
HifiWallet.NeedsLogIn {
id: needsLogIn;
visible: root.activeView === "needsLogIn";
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToWallet: {
sendToScript(msg);
}
}
}
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
}
}
//
// "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: "<b>Your wallet isn't set up.</b><br><br>Set up your Wallet (no credit card necessary) to claim your <b>free HFC</b> " +
"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;
//
// FILTER BAR START
//
Item {
id: filterBarContainer;
// Size
height: 40;
// Anchors
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.right: parent.right;
anchors.rightMargin: 8;
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;
visible: purchasesModel.count !== 0;
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});
}
}
}
}
}
Item {
id: noPurchasesAlertContainer;
visible: !purchasesContentsList.visible && root.purchasesReceived;
anchors.top: filterBarContainer.bottom;
anchors.topMargin: 12;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width;
// Explanitory text
RalewayRegular {
id: haventPurchasedYet;
text: "<b>You haven't purchased anything yet!</b><br><br>Get an item from <b>Marketplace</b> to add it to your <b>Purchases</b>.";
// Text size
size: 22;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 150;
anchors.left: parent.left;
anchors.leftMargin: 24;
anchors.right: parent.right;
anchors.rightMargin: 24;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
}
// "Set Up" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: haventPurchasedYet.bottom;
anchors.topMargin: 20;
anchors.horizontalCenter: parent.horizontalCenter;
width: parent.width * 2 / 3;
height: 50;
text: "Visit Marketplace";
onClicked: {
sendToScript({method: 'purchases_goToMarketplaceClicked'});
}
}
}
}
//
// 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
//
}

View file

@ -0,0 +1,87 @@
//
// SendMoney.qml
// qml/hifi/commerce/wallet
//
// SendMoney
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
Hifi.QmlCommerce {
id: commerce;
}
// "Unavailable"
RalewayRegular {
id: helpText;
text: "Help me!";
// Anchors
anchors.fill: parent;
// Text size
size: 24;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
HifiControlsUit.Button {
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.bottom: helpText.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
height: 50;
width: 250;
text: "Testing: Reset Wallet!";
onClicked: {
commerce.reset();
sendSignalToWallet({method: 'walletReset'});
}
}
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,188 @@
//
// NeedsLogIn.qml
// qml/hifi/commerce/wallet
//
// NeedsLogIn
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
Hifi.QmlCommerce {
id: commerce;
}
//
// LOGIN PAGE START
//
Item {
id: loginPageContainer;
// Anchors
anchors.fill: parent;
Item {
id: loginTitle;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
text: "HIFI COMMERCE - LOGIN";
// 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;
}
}
// Text below title bar
RalewaySemiBold {
id: loginTitleHelper;
text: "Please Log In to High Fidelity";
// Text size
size: 24;
// Anchors
anchors.top: loginTitle.bottom;
anchors.topMargin: 100;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 50;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
// Text below helper text
RalewayRegular {
id: loginDetailText;
text: "To buy/sell items on the <b>Marketplace</b>, or to use your <b>Wallet</b>, you must first log in to High Fidelity.";
// Text size
size: 18;
// Anchors
anchors.top: loginTitleHelper.bottom;
anchors.topMargin: 25;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 50;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
Item {
// Size
width: root.width;
height: 70;
// Anchors
anchors.top: loginDetailText.bottom;
anchors.topMargin: 40;
anchors.left: parent.left;
// "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: 'needsLogIn_cancelClicked'});
}
}
// "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: "Log In"
onClicked: {
sendToScript({method: 'needsLogIn_loginClicked'});
}
}
}
}
//
// LOGIN PAGE END
//
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,127 @@
//
// NotSetUp.qml
// qml/hifi/commerce/wallet
//
// NotSetUp
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
Hifi.QmlCommerce {
id: commerce;
}
//
// TAB CONTENTS START
//
// Text below title bar
RalewaySemiBold {
id: notSetUpText;
text: "Your Wallet Account Has Not Been Set Up";
// Text size
size: 22;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 100;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 50;
width: paintedWidth;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
// Explanitory text
RalewayRegular {
text: "To buy and sell items in High Fidelity Coin (HFC), you first need " +
"to set up your wallet.<br><b>You do not need to submit a credit card or personal information to set up your wallet.</b>";
// Text size
size: 18;
// Anchors
anchors.top: notSetUpText.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 30;
anchors.right: parent.right;
anchors.rightMargin: 30;
height: 100;
width: paintedWidth;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
// "Set Up" button
HifiControlsUit.Button {
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 150;
anchors.horizontalCenter: parent.horizontalCenter;
width: parent.width/2;
height: 50;
text: "Set Up My Wallet";
onClicked: {
sendSignalToWallet({method: 'setUpClicked'});
}
}
//
// TAB CONTENTS END
//
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,283 @@
//
// PassphraseSelection.qml
// qml/hifi/commerce/wallet
//
// PassphraseSelection
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
}
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
passphrasePageSecurityImage.source = "";
passphrasePageSecurityImage.source = "image://security/securityImage";
}
onPassphraseSetupStatusResult: {
sendMessageToLightbox({method: 'statusResult', status: passphraseIsSetup});
}
}
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
passphraseField.focus = true;
sendMessageToLightbox({method: 'disableHmdPreview'});
} else {
sendMessageToLightbox({method: 'maybeEnableHmdPreview'});
}
}
SecurityImageModel {
id: gridModel;
}
HifiControlsUit.TextField {
id: passphraseField;
anchors.top: parent.top;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.leftMargin: 16;
width: 280;
height: 50;
echoMode: TextInput.Password;
placeholderText: "passphrase";
onFocusChanged: {
if (focus) {
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'});
} else if (!passphraseFieldAgain.focus) {
sendMessageToLightbox({method: 'walletSetup_lowerKeyboard'});
}
}
MouseArea {
anchors.fill: parent;
onClicked: {
parent.focus = true;
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'});
}
}
onAccepted: {
passphraseFieldAgain.focus = true;
}
}
HifiControlsUit.TextField {
id: passphraseFieldAgain;
anchors.top: passphraseField.bottom;
anchors.topMargin: 10;
anchors.left: passphraseField.left;
anchors.right: passphraseField.right;
height: 50;
echoMode: TextInput.Password;
placeholderText: "re-enter passphrase";
onFocusChanged: {
if (focus) {
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'});
} else if (!passphraseField.focus) {
sendMessageToLightbox({method: 'walletSetup_lowerKeyboard'});
}
}
MouseArea {
anchors.fill: parent;
onClicked: {
parent.focus = true;
sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'});
}
}
onAccepted: {
focus = false;
}
}
// Security Image
Item {
id: securityImageContainer;
// Anchors
anchors.top: passphraseField.top;
anchors.left: passphraseField.right;
anchors.leftMargin: 12;
anchors.right: parent.right;
Image {
id: passphrasePageSecurityImage;
anchors.top: parent.top;
anchors.horizontalCenter: parent.horizontalCenter;
height: 75;
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
source: "image://security/securityImage";
cache: false;
onVisibleChanged: {
commerce.getSecurityImage();
}
}
Image {
id: topSecurityImageOverlay;
source: "images/lockOverlay.png";
width: passphrasePageSecurityImage.width * 0.45;
height: passphrasePageSecurityImage.height * 0.45;
anchors.bottom: passphrasePageSecurityImage.bottom;
anchors.right: passphrasePageSecurityImage.right;
mipmap: true;
opacity: 0.9;
}
// "Security image" text below pic
RalewayRegular {
text: "security image";
// Text size
size: 12;
// Anchors
anchors.top: passphrasePageSecurityImage.bottom;
anchors.topMargin: 4;
anchors.left: securityImageContainer.left;
anchors.right: securityImageContainer.right;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
// Error text below TextFields
RalewaySemiBold {
id: errorText;
text: "";
// Text size
size: 16;
// Anchors
anchors.top: passphraseFieldAgain.bottom;
anchors.topMargin: 0;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 30;
// Style
color: hifi.colors.redHighlight;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Text below TextFields
RalewaySemiBold {
id: passwordReqs;
text: "Passphrase must be at least 4 characters";
// Text size
size: 16;
// Anchors
anchors.top: passphraseFieldAgain.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 30;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Show passphrase text
HifiControlsUit.CheckBox {
id: showPassphrase;
colorScheme: hifi.colorSchemes.dark;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.top: passwordReqs.bottom;
anchors.topMargin: 16;
height: 30;
text: "Show passphrase as plain text";
boxSize: 24;
onClicked: {
passphraseField.echoMode = checked ? TextInput.Normal : TextInput.Password;
passphraseFieldAgain.echoMode = checked ? TextInput.Normal : TextInput.Password;
}
}
// Text below checkbox
RalewayRegular {
text: "Your passphrase is used to encrypt your private keys. <b>Please write it down.</b> If it is lost, you will not be able to recover it.";
// Text size
size: 16;
// Anchors
anchors.top: showPassphrase.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
function validateAndSubmitPassphrase() {
if (passphraseField.text.length < 4) {
setErrorText("Passphrase too short.");
return false;
} else if (passphraseField.text !== passphraseFieldAgain.text) {
setErrorText("Passphrases don't match.");
return false;
} else {
setErrorText("");
commerce.setPassphrase(passphraseField.text);
return true;
}
}
function setErrorText(text) {
errorText.text = text;
}
function clearPassphraseFields() {
passphraseField.text = "";
passphraseFieldAgain.text = "";
setErrorText("");
}
signal sendMessageToLightbox(var msg);
}

View file

@ -0,0 +1,176 @@
//
// PassphraseSelectionLightbox.qml
// qml/hifi/commerce/wallet
//
// PassphraseSelectionLightbox
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: root;
// Style
color: hifi.colors.baseGray;
//
// SECURE PASSPHRASE SELECTION START
//
Item {
id: choosePassphraseContainer;
// Anchors
anchors.fill: parent;
Item {
id: passphraseTitle;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
text: "CHANGE PASSPHRASE";
// 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;
}
}
// Text below title bar
RalewaySemiBold {
id: passphraseTitleHelper;
text: "Choose a Secure Passphrase";
// Text size
size: 24;
// Anchors
anchors.top: passphraseTitle.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 50;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
PassphraseSelection {
id: passphraseSelection;
anchors.top: passphraseTitleHelper.bottom;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: passphraseNavBar.top;
Connections {
onSendMessageToLightbox: {
if (msg.method === 'statusResult') {
if (msg.status) {
// Success submitting new passphrase
root.resetSubmitButton();
root.visible = false;
} else {
// Error submitting new passphrase
root.resetSubmitButton();
passphraseSelection.setErrorText("Backend error");
}
} else {
sendSignalToWallet(msg);
}
}
}
}
// Navigation Bar
Item {
id: passphraseNavBar;
// Size
width: parent.width;
height: 100;
// Anchors:
anchors.left: parent.left;
anchors.bottom: parent.bottom;
// "Cancel" button
HifiControlsUit.Button {
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: 100;
text: "Cancel"
onClicked: {
root.visible = false;
}
}
// "Submit" button
HifiControlsUit.Button {
id: passphraseSubmitButton;
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: 100;
text: "Submit";
onClicked: {
if (passphraseSelection.validateAndSubmitPassphrase()) {
passphraseSubmitButton.text = "Submitting...";
passphraseSubmitButton.enabled = false;
}
}
}
}
}
//
// SECURE PASSPHRASE SELECTION END
//
signal sendSignalToWallet(var msg);
function resetSubmitButton() {
passphraseSubmitButton.enabled = true;
passphraseSubmitButton.text = "Submit";
}
function clearPassphraseFields() {
passphraseSelection.clearPassphraseFields();
}
}

View file

@ -0,0 +1,335 @@
//
// Security.qml
// qml/hifi/commerce/wallet
//
// Security
//
// Created by Zach Fox on 2017-08-18
// 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 QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
if (exists) { // "If security image is set up"
var path = "image://security/securityImage";
topSecurityImage.source = "";
topSecurityImage.source = path;
changeSecurityImageImage.source = "";
changeSecurityImageImage.source = path;
}
}
onKeyFilePathIfExistsResult: {
keyFilePath.text = path;
}
}
SecurityImageModel {
id: securityImageModel;
}
// Username Text
RalewayRegular {
id: usernameText;
text: Account.username;
// Text size
size: 24;
// Style
color: hifi.colors.faintGray;
elide: Text.ElideRight;
// Anchors
anchors.top: securityImageContainer.top;
anchors.bottom: securityImageContainer.bottom;
anchors.left: parent.left;
anchors.right: securityImageContainer.left;
}
// Security Image
Item {
id: securityImageContainer;
// Anchors
anchors.top: parent.top;
anchors.right: parent.right;
width: 75;
height: childrenRect.height;
onVisibleChanged: {
if (visible) {
commerce.getSecurityImage();
}
}
Image {
id: topSecurityImage;
// Anchors
anchors.top: parent.top;
anchors.horizontalCenter: parent.horizontalCenter;
height: parent.width - 10;
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
source: "image://security/securityImage";
cache: false;
}
Image {
id: topSecurityImageMask;
source: "images/lockOverlay.png";
width: topSecurityImage.width * 0.45;
height: topSecurityImage.height * 0.45;
anchors.bottom: topSecurityImage.bottom;
anchors.right: topSecurityImage.right;
mipmap: true;
opacity: 0.9;
}
// "Security image" text below pic
RalewayRegular {
text: "security image";
// Text size
size: 12;
// Anchors
anchors.top: topSecurityImage.bottom;
anchors.topMargin: 4;
anchors.left: securityImageContainer.left;
anchors.right: securityImageContainer.right;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
Item {
id: securityContainer;
anchors.top: securityImageContainer.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.right: parent.right;
height: childrenRect.height;
RalewayRegular {
id: securityText;
text: "Security";
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 30;
// Text size
size: 22;
// Style
color: hifi.colors.faintGray;
}
Item {
id: changePassphraseContainer;
anchors.top: securityText.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
anchors.right: parent.right;
height: 75;
Image {
id: changePassphraseImage;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
height: parent.height;
width: height;
source: "images/lockOverlay.png";
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
visible: false;
}
ColorOverlay {
anchors.fill: changePassphraseImage;
source: changePassphraseImage;
color: "white"
}
// "Change Passphrase" button
HifiControlsUit.Button {
id: changePassphraseButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: changePassphraseImage.right;
anchors.leftMargin: 16;
width: 250;
height: 50;
text: "Change My Passphrase";
onClicked: {
sendSignalToWallet({method: 'walletSecurity_changePassphrase'});
}
}
}
Item {
id: changeSecurityImageContainer;
anchors.top: changePassphraseContainer.bottom;
anchors.topMargin: 8;
anchors.left: parent.left;
anchors.right: parent.right;
height: 75;
Image {
id: changeSecurityImageImage;
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
height: parent.height;
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
source: "image://security/securityImage";
}
// "Change Security Image" button
HifiControlsUit.Button {
id: changeSecurityImageButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.verticalCenter: parent.verticalCenter;
anchors.left: changeSecurityImageImage.right;
anchors.leftMargin: 16;
width: 250;
height: 50;
text: "Change My Security Image";
onClicked: {
sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'});
}
}
}
}
Item {
id: yourPrivateKeysContainer;
anchors.top: securityContainer.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
anchors.right: parent.right;
height: childrenRect.height;
RalewaySemiBold {
id: yourPrivateKeysText;
text: "Your Private Keys";
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 30;
// Text size
size: 22;
// Style
color: hifi.colors.faintGray;
}
// Text below "your private keys"
RalewayRegular {
id: explanitoryText;
text: "Your money and purchases are secured with private keys that only you " +
"have access to. <b>If they are lost, you will not be able to access your money or purchases. " +
"To safeguard your private keys, back up this file regularly:</b>";
// Text size
size: 18;
// Anchors
anchors.top: yourPrivateKeysText.bottom;
anchors.topMargin: 10;
anchors.left: parent.left;
anchors.right: parent.right;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
HifiControlsUit.TextField {
id: keyFilePath;
anchors.top: explanitoryText.bottom;
anchors.topMargin: 10;
anchors.left: parent.left;
anchors.right: clipboardButton.left;
height: 40;
readOnly: true;
onVisibleChanged: {
if (visible) {
commerce.getKeyFilePathIfExists();
}
}
}
HifiControlsUit.Button {
id: clipboardButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.right: parent.right;
anchors.top: keyFilePath.top;
anchors.bottom: keyFilePath.bottom;
width: height;
HiFiGlyphs {
text: hifi.glyphs.question;
// Size
size: parent.height*1.3;
// Anchors
anchors.fill: parent;
// Style
horizontalAlignment: Text.AlignHCenter;
color: enabled ? hifi.colors.white : hifi.colors.faintGray;
}
onClicked: {
Window.copyToClipboard(keyFilePath.text);
}
}
}
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -4,7 +4,7 @@
//
// SecurityImageModel
//
// Created by Zach Fox on 2017-08-15
// Created by Zach Fox on 2017-08-17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -16,27 +16,31 @@ import QtQuick 2.5
ListModel {
id: root;
ListElement{
sourcePath: "images/01cat.jpg"
sourcePath: "images/01.jpg"
securityImageEnumValue: 1;
}
ListElement{
sourcePath: "images/02car.jpg"
sourcePath: "images/02.jpg"
securityImageEnumValue: 2;
}
ListElement{
sourcePath: "images/03dog.jpg"
sourcePath: "images/03.jpg"
securityImageEnumValue: 3;
}
ListElement{
sourcePath: "images/04stars.jpg"
sourcePath: "images/04.jpg"
securityImageEnumValue: 4;
}
ListElement{
sourcePath: "images/05plane.jpg"
sourcePath: "images/05.jpg"
securityImageEnumValue: 5;
}
ListElement{
sourcePath: "images/06gingerbread.jpg"
sourcePath: "images/06.jpg"
securityImageEnumValue: 6;
}
function getImagePathFromImageID(imageID) {
return (imageID ? root.get(imageID - 1).sourcePath : "");
}
}

View file

@ -0,0 +1,98 @@
//
// SecurityImageSelection.qml
// qml/hifi/commerce/wallet
//
// SecurityImageSelection
//
// Created by Zach Fox on 2017-08-17
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
// This will cause a bug -- if you bring up security image selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
SecurityImageModel {
id: gridModel;
}
GridView {
id: securityImageGrid;
clip: true;
// Anchors
anchors.fill: parent;
currentIndex: -1;
cellWidth: width / 3;
cellHeight: height / 2;
model: gridModel;
delegate: Item {
width: securityImageGrid.cellWidth;
height: securityImageGrid.cellHeight;
Item {
anchors.fill: parent;
Image {
width: parent.width - 8;
height: parent.height - 8;
source: sourcePath;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.verticalCenter: parent.verticalCenter;
fillMode: Image.PreserveAspectFit;
mipmap: true;
}
}
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
onClicked: {
securityImageGrid.currentIndex = index;
}
}
}
highlight: Rectangle {
width: securityImageGrid.cellWidth;
height: securityImageGrid.cellHeight;
color: hifi.colors.blueHighlight;
}
}
//
// FUNCTION DEFINITIONS START
//
signal sendSignalToWallet(var msg);
function getImagePathFromImageID(imageID) {
return (imageID ? gridModel.getImagePathFromImageID(imageID) : "");
}
function getSelectedImageIndex() {
return gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue;
}
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,202 @@
//
// SecurityImageSelectionLightbox.qml
// qml/hifi/commerce/wallet
//
// SecurityImageSelectionLightbox
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: root;
property bool justSubmitted: false;
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
if (exists) { // Success submitting new security image
if (root.justSubmitted) {
root.resetSubmitButton();
root.visible = false;
root.justSubmitted = false;
}
} else if (root.justSubmitted) {
// Error submitting new security image.
root.resetSubmitButton();
root.justSubmitted = false;
}
}
}
//
// SECURITY IMAGE SELECTION START
//
Item {
id: securityImageContainer;
// Anchors
anchors.fill: parent;
Item {
id: securityImageTitle;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
text: "CHANGE SECURITY IMAGE";
// 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;
}
}
// Text below title bar
RalewaySemiBold {
id: securityImageTitleHelper;
text: "Choose a Security Picture:";
// Text size
size: 24;
// Anchors
anchors.top: securityImageTitle.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
height: 50;
width: paintedWidth;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
SecurityImageSelection {
id: securityImageSelection;
// Anchors
anchors.top: securityImageTitleHelper.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 280;
Connections {
onSendSignalToWallet: {
sendSignalToWallet(msg);
}
}
}
// Text below security images
RalewayRegular {
text: "<b>Your security picture shows you that the service asking for your passphrase is authorized.</b> You can change your secure picture at any time.";
// Text size
size: 18;
// Anchors
anchors.top: securityImageSelection.bottom;
anchors.topMargin: 40;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Navigation Bar
Item {
id: securityImageNavBar;
// Size
width: parent.width;
height: 100;
// Anchors:
anchors.left: parent.left;
anchors.bottom: parent.bottom;
// "Cancel" button
HifiControlsUit.Button {
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: 100;
text: "Cancel"
onClicked: {
root.visible = false;
}
}
// "Submit" button
HifiControlsUit.Button {
id: securityImageSubmitButton;
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: 100;
text: "Submit";
onClicked: {
root.justSubmitted = true;
securityImageSubmitButton.text = "Submitting...";
securityImageSubmitButton.enabled = false;
var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex())
commerce.chooseSecurityImage(securityImagePath);
}
}
}
}
//
// SECURITY IMAGE SELECTION END
//
signal sendSignalToWallet(var msg);
function resetSubmitButton() {
securityImageSubmitButton.enabled = true;
securityImageSubmitButton.text = "Submit";
}
}

View file

@ -0,0 +1,73 @@
//
// SendMoney.qml
// qml/hifi/commerce/wallet
//
// SendMoney
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
Hifi.QmlCommerce {
id: commerce;
}
// "Unavailable"
RalewayRegular {
text: "You currently cannot send money to other High Fidelity users.";
// Anchors
anchors.fill: parent;
// Text size
size: 24;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,590 @@
//
// Wallet.qml
// qml/hifi/commerce/wallet
//
// Wallet
//
// Created by Zach Fox on 2017-08-17
// 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
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: root;
property string activeView: "initialize";
property bool securityImageResultReceived: false;
property bool keyFilePathIfExistsResultReceived: false;
property bool keyboardRaised: false;
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.getSecurityImage();
commerce.getKeyFilePathIfExists();
}
}
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 = "walletHome";
}
}
onKeyFilePathIfExistsResult: {
keyFilePathIfExistsResultReceived = true;
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") {
root.activeView = "walletHome";
}
}
}
SecurityImageModel {
id: securityImageModel;
}
Rectangle {
id: walletSetupLightboxContainer;
visible: walletSetupLightbox.visible || passphraseSelectionLightbox.visible || securityImageSelectionLightbox.visible;
z: 998;
anchors.fill: parent;
color: "black";
opacity: 0.5;
}
WalletSetupLightbox {
id: walletSetupLightbox;
visible: false;
z: 998;
anchors.centerIn: walletSetupLightboxContainer;
width: walletSetupLightboxContainer.width - 50;
height: walletSetupLightboxContainer.height - 50;
Connections {
onSendSignalToWallet: {
if (msg.method === 'walletSetup_cancelClicked') {
walletSetupLightbox.visible = false;
} else if (msg.method === 'walletSetup_finished') {
root.activeView = "walletHome";
} else if (msg.method === 'walletSetup_raiseKeyboard') {
root.keyboardRaised = true;
} else if (msg.method === 'walletSetup_lowerKeyboard') {
root.keyboardRaised = false;
} else {
sendToScript(msg);
}
}
}
}
PassphraseSelectionLightbox {
id: passphraseSelectionLightbox;
visible: false;
z: 998;
anchors.centerIn: walletSetupLightboxContainer;
width: walletSetupLightboxContainer.width - 50;
height: walletSetupLightboxContainer.height - 50;
Connections {
onSendSignalToWallet: {
if (msg.method === 'walletSetup_raiseKeyboard') {
root.keyboardRaised = true;
} else if (msg.method === 'walletSetup_lowerKeyboard') {
root.keyboardRaised = false;
} else {
sendToScript(msg);
}
}
}
}
SecurityImageSelectionLightbox {
id: securityImageSelectionLightbox;
visible: false;
z: 998;
anchors.centerIn: walletSetupLightboxContainer;
width: walletSetupLightboxContainer.width - 50;
height: walletSetupLightboxContainer.height - 50;
Connections {
onSendSignalToWallet: {
sendToScript(msg);
}
}
}
//
// TITLE BAR START
//
Item {
id: titleBarContainer;
visible: !needsLogIn.visible;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
id: titleBarText;
text: "WALLET";
// 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;
}
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
}
//
// TITLE BAR END
//
//
// TAB CONTENTS START
//
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.getLoginStatus();
}
}
NeedsLogIn {
id: needsLogIn;
visible: root.activeView === "needsLogIn";
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToWallet: {
sendToScript(msg);
}
}
}
Connections {
target: GlobalServices
onMyUsernameChanged: {
commerce.getLoginStatus();
}
}
NotSetUp {
id: notSetUp;
visible: root.activeView === "notSetUp";
anchors.top: titleBarContainer.bottom;
anchors.bottom: tabButtonsContainer.top;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToWallet: {
if (msg.method === 'setUpClicked') {
walletSetupLightbox.visible = true;
}
}
}
}
WalletHome {
id: walletHome;
visible: root.activeView === "walletHome";
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 16;
anchors.bottom: tabButtonsContainer.top;
anchors.bottomMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
}
SendMoney {
id: sendMoney;
visible: root.activeView === "sendMoney";
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 16;
anchors.bottom: tabButtonsContainer.top;
anchors.bottomMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
}
Security {
id: security;
visible: root.activeView === "security";
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 16;
anchors.bottom: tabButtonsContainer.top;
anchors.bottomMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
Connections {
onSendSignalToWallet: {
if (msg.method === 'walletSecurity_changePassphrase') {
passphraseSelectionLightbox.visible = true;
passphraseSelectionLightbox.clearPassphraseFields();
} else if (msg.method === 'walletSecurity_changeSecurityImage') {
securityImageSelectionLightbox.visible = true;
}
}
}
}
Help {
id: help;
visible: root.activeView === "help";
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 16;
anchors.bottom: tabButtonsContainer.top;
anchors.bottomMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
Connections {
onSendSignalToWallet: {
if (msg.method === 'walletReset') {
sendToScript(msg);
}
}
}
}
//
// TAB CONTENTS END
//
//
// TAB BUTTONS START
//
Item {
id: tabButtonsContainer;
visible: !needsLogIn.visible;
property int numTabs: 5;
// Size
width: root.width;
height: 80;
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.top: parent.top;
}
// "WALLET HOME" tab button
Rectangle {
id: walletHomeButtonContainer;
visible: !notSetUp.visible;
color: root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: parent.width / tabButtonsContainer.numTabs;
RalewaySemiBold {
text: "WALLET HOME";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 4;
anchors.rightMargin: 4;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
MouseArea {
enabled: !walletSetupLightboxContainer.visible;
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
root.activeView = "walletHome";
tabButtonsContainer.resetTabButtonColors();
}
onEntered: parent.color = hifi.colors.blueHighlight;
onExited: parent.color = root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black;
}
}
// "SEND MONEY" tab button
Rectangle {
id: sendMoneyButtonContainer;
visible: !notSetUp.visible;
color: hifi.colors.black;
anchors.top: parent.top;
anchors.left: walletHomeButtonContainer.right;
anchors.bottom: parent.bottom;
width: parent.width / tabButtonsContainer.numTabs;
RalewaySemiBold {
text: "SEND MONEY";
// Text size
size: 14;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 4;
anchors.rightMargin: 4;
// Style
color: hifi.colors.lightGray50;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
// "EXCHANGE MONEY" tab button
Rectangle {
id: exchangeMoneyButtonContainer;
visible: !notSetUp.visible;
color: hifi.colors.black;
anchors.top: parent.top;
anchors.left: sendMoneyButtonContainer.right;
anchors.bottom: parent.bottom;
width: parent.width / tabButtonsContainer.numTabs;
RalewaySemiBold {
text: "EXCHANGE MONEY";
// Text size
size: 14;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 4;
anchors.rightMargin: 4;
// Style
color: hifi.colors.lightGray50;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
// "SECURITY" tab button
Rectangle {
id: securityButtonContainer;
visible: !notSetUp.visible;
color: root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black;
anchors.top: parent.top;
anchors.left: exchangeMoneyButtonContainer.right;
anchors.bottom: parent.bottom;
width: parent.width / tabButtonsContainer.numTabs;
RalewaySemiBold {
text: "SECURITY";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 4;
anchors.rightMargin: 4;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
MouseArea {
enabled: !walletSetupLightboxContainer.visible;
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
root.activeView = "security";
tabButtonsContainer.resetTabButtonColors();
}
onEntered: parent.color = hifi.colors.blueHighlight;
onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black;
}
}
// "HELP" tab button
Rectangle {
id: helpButtonContainer;
visible: !notSetUp.visible;
color: root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black;
anchors.top: parent.top;
anchors.left: securityButtonContainer.right;
anchors.bottom: parent.bottom;
width: parent.width / tabButtonsContainer.numTabs;
RalewaySemiBold {
text: "HELP";
// Text size
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.fill: parent;
anchors.leftMargin: 4;
anchors.rightMargin: 4;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
MouseArea {
enabled: !walletSetupLightboxContainer.visible;
anchors.fill: parent;
hoverEnabled: enabled;
onClicked: {
root.activeView = "help";
tabButtonsContainer.resetTabButtonColors();
}
onEntered: parent.color = hifi.colors.blueHighlight;
onExited: parent.color = root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black;
}
}
function resetTabButtonColors() {
walletHomeButtonContainer.color = hifi.colors.black;
sendMoneyButtonContainer.color = hifi.colors.black;
securityButtonContainer.color = hifi.colors.black;
helpButtonContainer.color = hifi.colors.black;
if (root.activeView === "walletHome") {
walletHomeButtonContainer.color = hifi.colors.blueAccent;
} else if (root.activeView === "sendMoney") {
sendMoneyButtonContainer.color = hifi.colors.blueAccent;
} else if (root.activeView === "security") {
securityButtonContainer.color = hifi.colors.blueAccent;
} else if (root.activeView === "help") {
helpButtonContainer.color = hifi.colors.blueAccent;
}
}
}
//
// TAB BUTTONS END
//
Item {
id: keyboardContainer;
z: 999;
visible: keyboard.raised;
property bool punctuationMode: false;
anchors {
bottom: parent.bottom;
left: parent.left;
right: parent.right;
}
Image {
id: lowerKeyboardButton;
source: "images/lowerKeyboard.png";
anchors.horizontalCenter: parent.horizontalCenter;
anchors.bottom: keyboard.top;
height: 30;
width: 120;
MouseArea {
anchors.fill: parent;
onClicked: {
root.keyboardRaised = false;
}
}
}
HifiControlsUit.Keyboard {
id: keyboard;
raised: HMD.mounted && root.keyboardRaised;
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.
// 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendToScript(var message);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,330 @@
//
// WalletHome.qml
// qml/hifi/commerce/wallet
//
// WalletHome
//
// Created by Zach Fox on 2017-08-18
// 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
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
property bool historyReceived: false;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
if (exists) {
// just set the source again (to be sure the change was noticed)
securityImage.source = "";
securityImage.source = "image://security/securityImage";
}
}
onBalanceResult : {
balanceText.text = parseFloat(result.data.balance/100).toFixed(2);
}
onHistoryResult : {
historyReceived = true;
if (result.status === 'success') {
transactionHistoryModel.clear();
transactionHistoryModel.append(result.data.history);
}
}
}
SecurityImageModel {
id: securityImageModel;
}
Connections {
target: GlobalServices
onMyUsernameChanged: {
usernameText.text = Account.username;
}
}
// Username Text
RalewayRegular {
id: usernameText;
text: Account.username;
// Text size
size: 24;
// Style
color: hifi.colors.faintGray;
elide: Text.ElideRight;
// Anchors
anchors.top: securityImageContainer.top;
anchors.bottom: securityImageContainer.bottom;
anchors.left: parent.left;
anchors.right: hfcBalanceContainer.left;
}
// HFC Balance Container
Item {
id: hfcBalanceContainer;
// Anchors
anchors.top: securityImageContainer.top;
anchors.right: securityImageContainer.left;
anchors.rightMargin: 16;
width: 175;
height: 60;
Rectangle {
id: hfcBalanceField;
anchors.right: parent.right;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
height: parent.height - 15;
// "HFC" balance label
RalewayRegular {
id: balanceLabel;
text: "HFC";
// Text size
size: 20;
// Anchors
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: hfcBalanceField.right;
anchors.rightMargin: 4;
width: paintedWidth;
// Style
color: hifi.colors.darkGray;
// Alignment
horizontalAlignment: Text.AlignRight;
verticalAlignment: Text.AlignVCenter;
onVisibleChanged: {
if (visible) {
historyReceived = false;
commerce.balance();
commerce.history();
}
}
}
// Balance Text
FiraSansRegular {
id: balanceText;
text: "--";
// Text size
size: 28;
// Anchors
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: balanceLabel.left;
anchors.rightMargin: 4;
// Style
color: hifi.colors.darkGray;
// Alignment
horizontalAlignment: Text.AlignRight;
verticalAlignment: Text.AlignVCenter;
}
}
// "balance" text above field
RalewayRegular {
text: "balance";
// Text size
size: 12;
// Anchors
anchors.top: parent.top;
anchors.bottom: hfcBalanceField.top;
anchors.bottomMargin: 4;
anchors.left: hfcBalanceField.left;
anchors.right: hfcBalanceField.right;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
}
// Security Image
Item {
id: securityImageContainer;
// Anchors
anchors.top: parent.top;
anchors.right: parent.right;
width: 75;
height: childrenRect.height;
onVisibleChanged: {
if (visible) {
commerce.getSecurityImage();
}
}
Image {
id: securityImage;
// Anchors
anchors.top: parent.top;
anchors.horizontalCenter: parent.horizontalCenter;
height: parent.width - 10;
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
cache: false;
source: "image://security/securityImage";
}
Image {
id: securityImageOverlay;
source: "images/lockOverlay.png";
width: securityImage.width * 0.45;
height: securityImage.height * 0.45;
anchors.bottom: securityImage.bottom;
anchors.right: securityImage.right;
mipmap: true;
opacity: 0.9;
}
// "Security image" text below pic
RalewayRegular {
text: "security image";
// Text size
size: 12;
// Anchors
anchors.top: securityImage.bottom;
anchors.topMargin: 4;
anchors.left: securityImageContainer.left;
anchors.right: securityImageContainer.right;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
// Recent Activity
Item {
id: recentActivityContainer;
anchors.top: securityImageContainer.bottom;
anchors.topMargin: 8;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
RalewayRegular {
id: recentActivityText;
text: "Recent Activity";
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 30;
// Text size
size: 22;
// Style
color: hifi.colors.faintGray;
}
ListModel {
id: transactionHistoryModel;
}
Rectangle {
anchors.top: recentActivityText.bottom;
anchors.topMargin: 4;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
color: "white";
ListView {
id: transactionHistory;
anchors.centerIn: parent;
width: parent.width - 12;
height: parent.height - 12;
visible: transactionHistoryModel.count !== 0;
clip: true;
model: transactionHistoryModel;
delegate: Item {
width: parent.width;
height: transactionText.height + 30;
RalewayRegular {
id: transactionText;
text: model.text;
// Style
size: 18;
width: parent.width;
height: paintedHeight;
anchors.verticalCenter: parent.verticalCenter;
color: "black";
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: parent.bottom;
}
}
onAtYEndChanged: {
if (transactionHistory.atYEnd) {
console.log("User scrolled to the bottom of 'Recent Activity'.");
// Grab next page of results and append to model
}
}
}
// This should never be visible (since you immediately get 100 HFC)
RalewayRegular {
id: emptyTransationHistory;
size: 24;
visible: !transactionHistory.visible && root.historyReceived;
text: "Recent Activity Unavailable";
anchors.fill: parent;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
}
//
// FUNCTION DEFINITIONS START
//
//
// Function Name: fromScript()
//
// Relevant Variables:
// None
//
// Arguments:
// message: The message sent from the 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) {
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
}
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

@ -0,0 +1,502 @@
//
// WalletSetupLightbox.qml
// qml/hifi/commerce/wallet
//
// WalletSetupLightbox
//
// Created by Zach Fox on 2017-08-17
// 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
// references XXX from root context
Rectangle {
HifiConstants { id: hifi; }
id: root;
property string lastPage: "initialize";
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
if (!exists && root.lastPage === "securityImage") {
// ERROR! Invalid security image.
securityImageContainer.visible = true;
choosePassphraseContainer.visible = false;
}
}
onPassphraseSetupStatusResult: {
securityImageContainer.visible = false;
if (passphraseIsSetup) {
privateKeysReadyContainer.visible = true;
} else {
choosePassphraseContainer.visible = true;
}
}
onKeyFilePathIfExistsResult: {
keyFilePath.text = path;
}
}
//
// SECURITY IMAGE SELECTION START
//
Item {
id: securityImageContainer;
// Anchors
anchors.fill: parent;
Item {
id: securityImageTitle;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
text: "WALLET SETUP - STEP 1 OF 3";
// 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;
}
}
// Text below title bar
RalewaySemiBold {
id: securityImageTitleHelper;
text: "Choose a Security Picture:";
// Text size
size: 24;
// Anchors
anchors.top: securityImageTitle.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
height: 50;
width: paintedWidth;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
SecurityImageSelection {
id: securityImageSelection;
// Anchors
anchors.top: securityImageTitleHelper.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 280;
Connections {
onSendSignalToWallet: {
sendSignalToWallet(msg);
}
}
}
// Text below security images
RalewayRegular {
text: "<b>Your security picture shows you that the service asking for your passphrase is authorized.</b> You can change your secure picture at any time.";
// Text size
size: 18;
// Anchors
anchors.top: securityImageSelection.bottom;
anchors.topMargin: 40;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Navigation Bar
Item {
// Size
width: parent.width;
height: 100;
// Anchors:
anchors.left: parent.left;
anchors.bottom: parent.bottom;
// "Cancel" button
HifiControlsUit.Button {
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: 100;
text: "Cancel"
onClicked: {
sendSignalToWallet({method: 'walletSetup_cancelClicked'});
}
}
// "Next" button
HifiControlsUit.Button {
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: 100;
text: "Next";
onClicked: {
root.lastPage = "securityImage";
var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex())
commerce.chooseSecurityImage(securityImagePath);
securityImageContainer.visible = false;
choosePassphraseContainer.visible = true;
passphraseSelection.clearPassphraseFields();
}
}
}
}
//
// SECURITY IMAGE SELECTION END
//
//
// SECURE PASSPHRASE SELECTION START
//
Item {
id: choosePassphraseContainer;
visible: false;
// Anchors
anchors.fill: parent;
onVisibleChanged: {
if (visible) {
commerce.getPassphraseSetupStatus();
}
}
Item {
id: passphraseTitle;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
text: "WALLET SETUP - STEP 2 OF 3";
// 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;
}
}
// Text below title bar
RalewaySemiBold {
id: passphraseTitleHelper;
text: "Choose a Secure Passphrase";
// Text size
size: 24;
// Anchors
anchors.top: passphraseTitle.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 50;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
PassphraseSelection {
id: passphraseSelection;
anchors.top: passphraseTitleHelper.bottom;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.right: parent.right;
anchors.bottom: passphraseNavBar.top;
Connections {
onSendMessageToLightbox: {
if (msg.method === 'statusResult') {
} else {
sendSignalToWallet(msg);
}
}
}
}
// Navigation Bar
Item {
id: passphraseNavBar;
// Size
width: parent.width;
height: 100;
// Anchors:
anchors.left: parent.left;
anchors.bottom: parent.bottom;
// "Back" button
HifiControlsUit.Button {
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: 100;
text: "Back"
onClicked: {
root.lastPage = "choosePassphrase";
choosePassphraseContainer.visible = false;
securityImageContainer.visible = true;
}
}
// "Next" button
HifiControlsUit.Button {
id: passphrasePageNextButton;
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: 100;
text: "Next";
onClicked: {
if (passphraseSelection.validateAndSubmitPassphrase()) {
root.lastPage = "choosePassphrase";
commerce.balance(); // Do this here so that keys are generated. Order might change as backend changes?
choosePassphraseContainer.visible = false;
privateKeysReadyContainer.visible = true;
}
}
}
}
}
//
// SECURE PASSPHRASE SELECTION END
//
//
// PRIVATE KEYS READY START
//
Item {
id: privateKeysReadyContainer;
visible: false;
// Anchors
anchors.fill: parent;
Item {
id: keysReadyTitle;
// Size
width: parent.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.top: parent.top;
// Title Bar text
RalewaySemiBold {
text: "WALLET SETUP - STEP 3 OF 3";
// 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;
}
}
// Text below title bar
RalewaySemiBold {
id: keysReadyTitleHelper;
text: "Your Private Keys are Ready";
// Text size
size: 24;
// Anchors
anchors.top: keysReadyTitle.bottom;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 50;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
// Text below checkbox
RalewayRegular {
id: explanationText;
text: "Your money and purchases are secured with private keys that only you have access to. " +
"<b>If they are lost, you will not be able to access your money or purchases.</b><br><br>" +
"<b>To protect your privacy, High Fidelity has no access to your private keys and cannot " +
"recover them for any reason.<br><br>To safeguard your private keys, backup this file on a regular basis:</b>";
// Text size
size: 16;
// Anchors
anchors.top: keysReadyTitleHelper.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHLeft;
verticalAlignment: Text.AlignVCenter;
}
HifiControlsUit.TextField {
id: keyFilePath;
anchors.top: explanationText.bottom;
anchors.topMargin: 10;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: clipboardButton.left;
height: 40;
readOnly: true;
onVisibleChanged: {
if (visible) {
commerce.getKeyFilePathIfExists();
}
}
}
HifiControlsUit.Button {
id: clipboardButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.right: parent.right;
anchors.rightMargin: 16;
anchors.top: keyFilePath.top;
anchors.bottom: keyFilePath.bottom;
width: height;
HiFiGlyphs {
text: hifi.glyphs.question;
// Size
size: parent.height*1.3;
// Anchors
anchors.fill: parent;
// Style
horizontalAlignment: Text.AlignHCenter;
color: enabled ? hifi.colors.white : hifi.colors.faintGray;
}
onClicked: {
Window.copyToClipboard(keyFilePath.text);
}
}
// Navigation Bar
Item {
// Size
width: parent.width;
height: 100;
// Anchors:
anchors.left: parent.left;
anchors.bottom: parent.bottom;
// "Next" button
HifiControlsUit.Button {
id: keysReadyPageNextButton;
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: 100;
text: "Finish";
onClicked: {
root.visible = false;
sendSignalToWallet({method: 'walletSetup_finished'});
}
}
}
}
//
// PRIVATE KEYS READY END
//
//
// FUNCTION DEFINITIONS START
//
signal sendSignalToWallet(var msg);
//
// FUNCTION DEFINITIONS END
//
}

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

View file

@ -0,0 +1,10 @@
import QtQuick 2.0
import QtWebEngine 1.2
import "../../controls" as Controls
Controls.TabletWebView {
profile: WebEngineProfile { httpUserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"}
}

View file

@ -39,11 +39,24 @@ StackView {
property var rpcCounter: 0;
signal sendToScript(var message);
function rpc(method, parameters, callback) {
console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback)
rpcCalls[rpcCounter] = callback;
var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"};
sendToScript(message);
}
function fromScript(message) {
if (message.method === 'refreshFeeds') {
var feeds = [happeningNow, places, snapshots];
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
feeds.forEach(function(feed) {
Qt.callLater(feed.fillDestinations);
});
return;
}
var callback = rpcCalls[message.id];
if (!callback) {
console.log('No callback for message fromScript', JSON.stringify(message));

View file

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

View file

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

View file

@ -93,6 +93,11 @@ Item {
loader.source = "";
loader.source = "TabletWebView.qml";
}
function loadTabletWebBase() {
loader.source = "";
loader.source = "./BlocksWebView.qml";
}
function returnToPreviousApp() {
tabletApps.remove(currentApp);
@ -121,6 +126,9 @@ Item {
loader.item.url = url;
loader.item.scriptURL = injectedJavaScriptUrl;
tabletApps.append({"appUrl": "TabletWebView.qml", "isWebUrl": true, "scriptUrl": injectedJavaScriptUrl, "appWebUrl": url});
if (loader.item.hasOwnProperty("closeButtonVisible")) {
loader.item.closeButtonVisible = false;
}
}
// used to send a message from qml to interface script.

View file

@ -42,9 +42,17 @@ Windows.ScrollingWindow {
loader.source = "WindowWebView.qml";
}
function loadTabletWebBase() {
loader.source = "";
loader.source = "./BlocksWebView.qml";
}
function loadWebUrl(url, injectedJavaScriptUrl) {
loader.item.url = url;
loader.item.scriptURL = injectedJavaScriptUrl;
if (loader.item.hasOwnProperty("closeButtonVisible")) {
loader.item.closeButtonVisible = false;
}
}
// used to send a message from qml to interface script.

View file

@ -4,6 +4,7 @@ import QtQuick.Controls 1.4
StateImage {
id: button
property string captionColorOverride: ""
property bool buttonEnabled: true
property bool isActive: false
property bool isEntered: false
@ -97,7 +98,7 @@ StateImage {
Text {
id: caption
color: button.isActive ? "#000000" : "#ffffff"
color: captionColorOverride !== "" ? captionColorOverride: (button.isActive ? "#000000" : "#ffffff")
text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text)
font.bold: false
font.pixelSize: 9

View file

@ -0,0 +1,30 @@
//
// Created by Bradley Austin Davis on 2016/07/11
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
uniform sampler2D sampler;
struct OverlayData {
mat4 mvp;
float alpha;
};
layout(std140) uniform overlayBuffer {
OverlayData overlay;
};
in vec2 vTexCoord;
out vec4 FragColor;
void main() {
FragColor = texture(sampler, vTexCoord);
FragColor.a *= overlay.alpha;
if (FragColor.a <= 0.0) {
discard;
}
}

View file

@ -8,12 +8,7 @@
struct OverlayData {
mat4 mvp;
vec4 glowPoints;
vec4 glowColors[2];
vec4 resolutionRadiusAlpha;
vec4 extraGlowColor;
vec2 extraGlowPoint;
float alpha;
};
layout(std140) uniform overlayBuffer {
@ -25,11 +20,9 @@ mat4 mvp = overlay.mvp;
layout(location = 0) in vec3 Position;
layout(location = 3) in vec2 TexCoord;
out vec3 vPosition;
out vec2 vTexCoord;
void main() {
gl_Position = mvp * vec4(Position, 1);
vTexCoord = TexCoord;
vPosition = Position;
}

View file

@ -1,86 +0,0 @@
//
// Created by Bradley Austin Davis on 2016/07/11
// Copyright 2013-2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
uniform sampler2D sampler;
struct OverlayData {
mat4 mvp;
vec4 glowPoints;
vec4 glowColors[2];
vec4 resolutionRadiusAlpha;
vec4 extraGlowColor;
vec2 extraGlowPoint;
};
layout(std140) uniform overlayBuffer {
OverlayData overlay;
};
vec2 resolution = overlay.resolutionRadiusAlpha.xy;
float radius = overlay.resolutionRadiusAlpha.z;
float alpha = overlay.resolutionRadiusAlpha.w;
vec4 glowPoints = overlay.glowPoints;
vec4 glowColors[2] = overlay.glowColors;
vec2 extraGlowPoint = overlay.extraGlowPoint;
vec4 extraGlowColor = overlay.extraGlowColor;
in vec3 vPosition;
in vec2 vTexCoord;
out vec4 FragColor;
float easeInOutCubic(float f) {
const float d = 1.0;
const float b = 0.0;
const float c = 1.0;
float t = f;
if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b;
return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b;
}
void main() {
FragColor = texture(sampler, vTexCoord);
vec2 aspect = resolution;
aspect /= resolution.x;
float glowIntensity = 0.0;
float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect);
float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect);
float dist3 = distance(vTexCoord * aspect, extraGlowPoint * aspect);
float distX = min(dist1, dist2);
float dist = min(distX, dist3);
vec3 glowColor = glowColors[0].rgb;
if (dist2 < dist1) {
glowColor = glowColors[1].rgb;
}
if (dist3 < dist2) {
glowColor = extraGlowColor.rgb;
}
if (dist <= radius) {
glowIntensity = 1.0 - (dist / radius);
glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity));
glowIntensity = easeInOutCubic(glowIntensity);
glowIntensity = pow(glowIntensity, 0.5);
}
if (alpha <= 0.0) {
if (glowIntensity <= 0.0) {
discard;
}
FragColor = vec4(glowColor, glowIntensity);
return;
}
FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity);
FragColor.a *= alpha;
}

View file

@ -113,7 +113,6 @@
#include <plugins/SteamClientPlugin.h>
#include <plugins/InputConfiguration.h>
#include <RecordingScriptingInterface.h>
#include <RenderableWebEntityItem.h>
#include <UpdateSceneTask.h>
#include <RenderViewTask.h>
#include <SecondaryCamera.h>
@ -139,6 +138,7 @@
#include <display-plugins/CompositorHelper.h>
#include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h>
#include "AudioClient.h"
#include "audio/AudioScope.h"
@ -166,6 +166,7 @@
#include "scripting/WindowScriptingInterface.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/RatesScriptingInterface.h"
#include "scripting/SelectionScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
#endif
@ -193,6 +194,10 @@
#include <src/scripting/LimitlessVoiceRecognitionScriptingInterface.h>
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
#include <raypick/RayPickScriptingInterface.h>
#include <raypick/LaserPointerScriptingInterface.h>
#include "commerce/Ledger.h"
#include "commerce/Wallet.h"
#include "commerce/QmlCommerce.h"
@ -207,6 +212,73 @@ extern "C" {
}
#endif
enum ApplicationEvent {
// Execute a lambda function
Lambda = QEvent::User + 1,
// Trigger the next render
Render,
// Trigger the next idle
Idle,
};
class RenderEventHandler : public QObject {
using Parent = QObject;
Q_OBJECT
public:
RenderEventHandler(QOpenGLContext* context) {
_renderContext = new OffscreenGLCanvas();
_renderContext->setObjectName("RenderContext");
_renderContext->create(context);
if (!_renderContext->makeCurrent()) {
qFatal("Unable to make rendering context current");
}
_renderContext->doneCurrent();
// Deleting the object with automatically shutdown the thread
connect(qApp, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
// Transfer to a new thread
moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) {
hifi::qt::addBlockingForbiddenThread("Render", renderThread);
_renderContext->moveToThreadWithContext(renderThread);
qApp->_lastTimeRendered.start();
}, std::bind(&RenderEventHandler::initialize, this), QThread::HighestPriority);
}
private:
void initialize() {
PROFILE_SET_THREAD_NAME("Render");
if (!_renderContext->makeCurrent()) {
qFatal("Unable to make rendering context current on render thread");
}
}
void render() {
if (qApp->shouldPaint()) {
qApp->paintGL();
}
}
bool event(QEvent* event) override {
switch ((int)event->type()) {
case ApplicationEvent::Render:
render();
// Ensure we never back up the render events. Each render should be triggered only in response
// to the NEXT render event after the last render occured
QCoreApplication::removePostedEvents(this, ApplicationEvent::Render);
return true;
default:
break;
}
return Parent::event(event);
}
OffscreenGLCanvas* _renderContext { nullptr };
};
Q_LOGGING_CATEGORY(trace_app_input_mouse, "trace.app.input.mouse")
using namespace std;
@ -422,10 +494,10 @@ class LambdaEvent : public QEvent {
std::function<void()> _fun;
public:
LambdaEvent(const std::function<void()> & fun) :
QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) {
QEvent(static_cast<QEvent::Type>(ApplicationEvent::Lambda)), _fun(fun) {
}
LambdaEvent(std::function<void()> && fun) :
QEvent(static_cast<QEvent::Type>(Application::Lambda)), _fun(fun) {
QEvent(static_cast<QEvent::Type>(ApplicationEvent::Lambda)), _fun(fun) {
}
void call() const { _fun(); }
};
@ -604,10 +676,14 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<Snapshot>();
DependencyManager::set<CloseEventSender>();
DependencyManager::set<ResourceManager>();
DependencyManager::set<SelectionScriptingInterface>();
DependencyManager::set<ContextOverlayInterface>();
DependencyManager::set<Ledger>();
DependencyManager::set<Wallet>();
DependencyManager::set<LaserPointerScriptingInterface>();
DependencyManager::set<RayPickScriptingInterface>();
return previousSessionCrashed;
}
@ -951,6 +1027,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
@ -1359,8 +1438,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
if (entity && entity->wantsKeyboardFocus()) {
if (getEntities()->wantsKeyboardFocus(entityItemID)) {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
setKeyboardFocusEntity(entityItemID);
} else {
@ -1684,7 +1762,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_defaultSkybox->setCubemap(_defaultSkyboxTexture);
}
EntityItem::setEntitiesShouldFadeFunction([this]() {
EntityTreeRenderer::setEntitiesShouldFadeFunction([this]() {
SharedNodePointer entityServerNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::EntityServer);
return entityServerNode && !isPhysicsEnabled();
});
@ -1728,6 +1806,27 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
// Setup the mouse ray pick and related operators
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickID(_rayPickManager.createRayPick(
RayPickFilter(DependencyManager::get<RayPickScriptingInterface>()->PICK_ENTITIES() | DependencyManager::get<RayPickScriptingInterface>()->PICK_INCLUDE_NONCOLLIDABLE()),
0.0f, true));
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](QUuid rayPickID) {
RayToEntityIntersectionResult entityResult;
RayPickResult result = _rayPickManager.getPrevRayPickResult(rayPickID);
entityResult.intersects = result.type != DependencyManager::get<RayPickScriptingInterface>()->INTERSECTED_NONE();
if (entityResult.intersects) {
entityResult.intersection = result.intersection;
entityResult.distance = result.distance;
entityResult.surfaceNormal = result.surfaceNormal;
entityResult.entityID = result.objectID;
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
}
return entityResult;
});
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([&](QUuid rayPickID, bool value) {
_rayPickManager.setPrecisionPicking(rayPickID, value);
});
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
}
@ -2070,6 +2169,13 @@ void Application::initializeGL() {
_renderEngine->load();
_renderEngine->registerScene(_main3DScene);
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->setObjectName("MainThreadContext");
_offscreenContext->create(_glWidget->qglContext());
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
// The UI can't be created until the primary OpenGL
// context is created, because it needs to share
// texture resources
@ -2093,14 +2199,15 @@ void Application::initializeGL() {
_idleLoopStdev.reset();
_offscreenContext = new OffscreenGLCanvas();
_offscreenContext->setObjectName("MainThreadContext");
_offscreenContext->create(_glWidget->qglContext());
_offscreenContext->makeCurrent();
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
// Restore the primary GL content for the main thread
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make offscreen context current");
}
// update before the first render
update(0);
}
FrameTimingsScriptingInterface _frameTimingsScriptingInterface;
@ -2108,6 +2215,9 @@ FrameTimingsScriptingInterface _frameTimingsScriptingInterface;
extern void setupPreferences();
void Application::initializeUi() {
// Make sure all QML surfaces share the main thread GL context
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
AddressBarDialog::registerType();
ErrorDialog::registerType();
LoginDialog::registerType();
@ -2118,7 +2228,7 @@ void Application::initializeUi() {
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_glWidget->qglContext());
offscreenUi->create();
auto surfaceContext = offscreenUi->getSurfaceContext();
@ -2137,6 +2247,7 @@ void Application::initializeUi() {
qApp->quit();
});
setupPreferences();
// For some reason there is already an "Application" object in the QML context,
@ -2209,6 +2320,7 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
@ -2260,14 +2372,12 @@ void Application::initializeUi() {
void Application::paintGL() {
// Some plugins process message events, allowing paintGL to be called reentrantly.
if (_inPaint || _aboutToQuit || _window->isMinimized()) {
if (_aboutToQuit || _window->isMinimized()) {
return;
}
_inPaint = true;
Finally clearFlag([this] { _inPaint = false; });
_frameCount++;
_lastTimeRendered.start();
auto lastPaintBegin = usecTimestampNow();
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_frameCount);
@ -2283,18 +2393,11 @@ void Application::paintGL() {
displayPlugin = getActiveDisplayPlugin();
}
{
PROFILE_RANGE(render, "/offscreenMakeCurrent");
// FIXME not needed anymore?
_offscreenContext->makeCurrent();
}
{
PROFILE_RANGE(render, "/pluginBeginFrameRender");
// If a display plugin loses it's underlying support, it
// needs to be able to signal us to not use it
if (!displayPlugin->beginFrameRender(_frameCount)) {
_inPaint = false;
updateDisplayMode();
return;
}
@ -2362,12 +2465,6 @@ void Application::paintGL() {
auto myAvatar = getMyAvatar();
boomOffset = myAvatar->getScale() * myAvatar->getBoomLength() * -IDENTITY_FORWARD;
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
cameraMenuChanged();
}
// The render mode is default or mirror if the camera is in mirror mode, assigned further below
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
@ -2468,6 +2565,7 @@ void Application::paintGL() {
finalFramebuffer = framebufferCache->getFramebuffer();
}
mat4 eyeProjections[2];
{
PROFILE_RANGE(render, "/mainRender");
PerformanceTimer perfTimer("mainRender");
@ -2489,7 +2587,6 @@ void Application::paintGL() {
_myCamera.setProjection(displayPlugin->getCullingProjection(_myCamera.getProjection()));
renderArgs._context->enableStereo(true);
mat4 eyeOffsets[2];
mat4 eyeProjections[2];
auto baseProjection = renderArgs.getViewFrustum().getProjection();
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
float IPDScale = hmdInterface->getIPDScale();
@ -2520,6 +2617,19 @@ void Application::paintGL() {
displaySide(&renderArgs, _myCamera);
}
gpu::Batch postCompositeBatch;
{
PROFILE_RANGE(render, "/postComposite");
PerformanceTimer perfTimer("postComposite");
renderArgs._batch = &postCompositeBatch;
renderArgs._batch->setViewportTransform(ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height()));
renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView());
for_each_eye([&](Eye eye) {
renderArgs._batch->setProjectionTransform(eyeProjections[eye]);
_overlays.render3DHUDOverlays(&renderArgs);
});
}
auto frame = _gpuContext->endFrame();
frame->frameIndex = _frameCount;
frame->framebuffer = finalFramebuffer;
@ -2527,6 +2637,7 @@ void Application::paintGL() {
DependencyManager::get<FramebufferCache>()->releaseFramebuffer(framebuffer);
};
frame->overlay = _applicationOverlay.getOverlayTexture();
frame->postCompositeBatch = postCompositeBatch;
// deliver final scene rendering commands to the display plugin
{
PROFILE_RANGE(render, "/pluginOutput");
@ -2661,15 +2772,6 @@ void Application::resizeGL() {
QMutexLocker viewLocker(&_viewMutex);
_myCamera.loadViewFrustum(_viewFrustum);
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto uiSize = displayPlugin->getRecommendedUiSize();
// Bit of a hack since there's no device pixel ratio change event I can find.
if (offscreenUi->size() != fromGlm(uiSize)) {
qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize;
offscreenUi->resize(fromGlm(uiSize), true);
_offscreenContext->makeCurrent();
}
}
void Application::handleSandboxStatus(QNetworkReply* reply) {
@ -2830,64 +2932,39 @@ bool Application::importSVOFromURL(const QString& urlString) {
return true;
}
void Application::onPresent(quint32 frameCount) {
if (shouldPaint()) {
postEvent(this, new QEvent(static_cast<QEvent::Type>(Idle)), Qt::HighEventPriority);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
}
bool Application::importFromZIP(const QString& filePath) {
qDebug() << "A zip file has been dropped in: " << filePath;
QUrl empty;
qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true);
// handle Blocks download from Marketplace
if (filePath.contains("vr.google.com/downloads")) {
addAssetToWorldFromURL(filePath);
} else {
qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true, false);
}
return true;
}
bool _renderRequested { false };
bool Application::event(QEvent* event) {
if (!Menu::getInstance()) {
return false;
void Application::onPresent(quint32 frameCount) {
postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority);
if (_renderEventHandler && !isAboutToQuit()) {
postEvent(_renderEventHandler, new QEvent((QEvent::Type)ApplicationEvent::Render));
}
}
int type = event->type();
switch (type) {
case Event::Lambda:
static_cast<LambdaEvent*>(event)->call();
return true;
static inline bool isKeyEvent(QEvent::Type type) {
return type == QEvent::KeyPress || type == QEvent::KeyRelease;
}
// Explicit idle keeps the idle running at a lower interval, but without any rendering
// see (windowMinimizedChanged)
case Event::Idle:
idle();
// Clear the event queue of pending idle calls
removePostedEvents(this, Idle);
return true;
case Event::Paint:
// NOTE: This must be updated as close to painting as possible,
// or AvatarInputs will mysteriously move to the bottom-right
AvatarInputs::getInstance()->update();
paintGL();
// Clear the event queue of pending paint calls
removePostedEvents(this, Paint);
return true;
default:
break;
}
{
if (!_keyboardFocusedEntity.get().isInvalidID()) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
//auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedEntity.get());
if (entity && entity->getEventHandler()) {
bool Application::handleKeyEventForFocusedEntityOrOverlay(QEvent* event) {
if (!_keyboardFocusedEntity.get().isInvalidID()) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
auto eventHandler = getEntities()->getEventHandler(_keyboardFocusedEntity.get());
if (eventHandler) {
event->setAccepted(false);
QCoreApplication::sendEvent(entity->getEventHandler(), event);
QCoreApplication::sendEvent(eventHandler, event);
if (event->isAccepted()) {
_lastAcceptedKeyPress = usecTimestampNow();
return true;
@ -2895,20 +2972,17 @@ bool Application::event(QEvent* event) {
}
break;
}
default:
break;
}
default:
break;
}
}
{
if (_keyboardFocusedOverlay.get() != UNKNOWN_OVERLAY_ID) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
if (_keyboardFocusedOverlay.get() != UNKNOWN_OVERLAY_ID) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
// Only Web overlays can have focus.
auto overlay =
std::dynamic_pointer_cast<Web3DOverlay>(getOverlays().getOverlay(_keyboardFocusedOverlay.get()));
auto overlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlays().getOverlay(_keyboardFocusedOverlay.get()));
if (overlay && overlay->getEventHandler()) {
event->setAccepted(false);
QCoreApplication::sendEvent(overlay->getEventHandler(), event);
@ -2917,15 +2991,52 @@ bool Application::event(QEvent* event) {
return true;
}
}
break;
}
default:
break;
}
break;
default:
break;
}
}
switch (event->type()) {
return false;
}
bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) {
QUrl url = fileEvent->url();
if (!url.isEmpty()) {
QString urlString = url.toString();
if (canAcceptURL(urlString)) {
return acceptURL(urlString);
}
}
return false;
}
bool Application::event(QEvent* event) {
if (!Menu::getInstance()) {
return false;
}
// Allow focused Entities and Overlays to handle keyboard input
if (isKeyEvent(event->type()) && handleKeyEventForFocusedEntityOrOverlay(event)) {
return true;
}
int type = event->type();
switch (type) {
case ApplicationEvent::Lambda:
static_cast<LambdaEvent*>(event)->call();
return true;
// Explicit idle keeps the idle running at a lower interval, but without any rendering
// see (windowMinimizedChanged)
case ApplicationEvent::Idle:
idle();
// Don't process extra idle events that arrived in the event queue while we were doing this idle
QCoreApplication::removePostedEvents(this, ApplicationEvent::Idle);
return true;
case QEvent::MouseMove:
mouseMoveEvent(static_cast<QMouseEvent*>(event));
return true;
@ -2966,28 +3077,17 @@ bool Application::event(QEvent* event) {
case QEvent::Drop:
dropEvent(static_cast<QDropEvent*>(event));
return true;
case QEvent::FileOpen:
if (handleFileOpenEvent(static_cast<QFileOpenEvent*>(event))) {
return true;
}
break;
default:
break;
}
// handle custom URL
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
QUrl url = fileEvent->url();
if (!url.isEmpty()) {
QString urlString = url.toString();
if (canAcceptURL(urlString)) {
return acceptURL(urlString);
}
}
return false;
}
if (HFActionEvent::types().contains(event->type())) {
_controllerScriptingInterface->handleMetaEvent(static_cast<HFMetaEvent*>(event));
}
@ -3690,11 +3790,12 @@ bool Application::acceptSnapshot(const QString& urlString) {
static uint32_t _renderedFrameIndex { INVALID_FRAME };
bool Application::shouldPaint() {
bool Application::shouldPaint() const {
if (_aboutToQuit) {
return false;
}
auto displayPlugin = getActiveDisplayPlugin();
#ifdef DEBUG_PAINT_DELAY
@ -3710,15 +3811,14 @@ bool Application::shouldPaint() {
(float)paintDelaySamples / paintDelayUsecs << "us";
}
#endif
// Throttle if requested
if (displayPlugin->isThrottled() && (_lastTimeUpdated.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
if (displayPlugin->isThrottled() && (_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
return false;
}
// Sync up the _renderedFrameIndex
_renderedFrameIndex = displayPlugin->presentCount();
return true;
}
@ -3928,7 +4028,6 @@ void setupCpuMonitorThread() {
#endif
void Application::idle() {
PerformanceTimer perfTimer("idle");
@ -3959,9 +4058,17 @@ void Application::idle() {
});
#endif
auto displayPlugin = getActiveDisplayPlugin();
if (displayPlugin) {
auto uiSize = displayPlugin->getRecommendedUiSize();
// Bit of a hack since there's no device pixel ratio change event I can find.
if (offscreenUi->size() != fromGlm(uiSize)) {
qCDebug(interfaceapp) << "Device pixel ratio changed, triggering resize to " << uiSize;
offscreenUi->resize(fromGlm(uiSize), true);
_offscreenContext->makeCurrent();
}
}
if (displayPlugin) {
PROFILE_COUNTER_IF_CHANGED(app, "present", float, displayPlugin->presentRate());
}
@ -4009,6 +4116,10 @@ void Application::idle() {
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
if (!_offscreenContext->makeCurrent()) {
qFatal("Unable to make main thread context current");
}
{
PerformanceTimer perfTimer("update");
PerformanceWarning warn(showWarnings, "Application::idle()... update()");
@ -4071,6 +4182,13 @@ void Application::idle() {
}
_overlayConductor.update(secondsSinceLastUpdate);
auto myAvatar = getMyAvatar();
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !(myAvatar->getBoomLength() <= MyAvatar::ZOOM_MIN));
cameraMenuChanged();
}
}
ivec2 Application::getMouse() const {
@ -4325,7 +4443,6 @@ void Application::init() {
_timerStart.start();
_lastTimeUpdated.start();
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
// when +connect_lobby in command line, join steam lobby
const QString STEAM_LOBBY_COMMAND_LINE_KEY = "+connect_lobby";
@ -4374,14 +4491,7 @@ void Application::init() {
// Make sure any new sounds are loaded as soon as know about them.
connect(tree.get(), &EntityTree::newCollisionSoundURL, this, [this](QUrl newURL, EntityItemID id) {
EntityTreePointer tree = getEntities()->getTree();
if (auto entity = tree->findEntityByEntityItemID(id)) {
auto sound = DependencyManager::get<SoundCache>()->getSound(newURL);
auto renderable = entity->getRenderableInterface();
if (renderable) {
renderable->setCollisionSound(sound);
}
}
getEntities()->setCollisionSound(id, DependencyManager::get<SoundCache>()->getSound(newURL));
}, Qt::QueuedConnection);
connect(getMyAvatar().get(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) {
if (auto avatar = getMyAvatar()) {
@ -4440,10 +4550,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<Avatar>(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<Avatar>(lookingAt)->getHead();
auto lookingAtHead = avatar->getHead();
const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE;
glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD;
@ -4692,9 +4805,12 @@ void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
auto properties = entityScriptingInterface->getEntityProperties(entityItemID);
if (!properties.getLocked() && properties.getVisible()) {
auto entity = getEntities()->getTree()->findEntityByID(entityItemID);
if (entity && entity->wantsKeyboardFocus()) {
entity->setProxyWindow(_window->windowHandle());
auto entities = getEntities();
auto entityId = _keyboardFocusedEntity.get();
if (entities->wantsKeyboardFocus(entityId)) {
entities->setProxyWindow(entityId, _window->windowHandle());
auto entity = getEntities()->getEntity(entityId);
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->pluginFocusOutEvent();
}
@ -4970,7 +5086,7 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("physics");
{
PROFILE_RANGE_EX(simulation_physics, "UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation_physics, "UpdateStates", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("updateStates)");
static VectorOfMotionStates motionStates;
@ -5039,7 +5155,7 @@ void Application::update(float deltaTime) {
// NOTE: the getEntities()->update() call below will wait for lock
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
getEntities()->update(); // update the models...
getEntities()->update(true); // update the models...
}
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
@ -5052,6 +5168,9 @@ void Application::update(float deltaTime) {
_physicsEngine->dumpStatsIfNecessary();
}
}
} else {
// update the rendering without any simulation
getEntities()->update(false);
}
// AvatarManager update
@ -5078,6 +5197,16 @@ void Application::update(float deltaTime) {
_overlays.update(deltaTime);
}
{
PROFILE_RANGE(app, "RayPickManager");
_rayPickManager.update();
}
{
PROFILE_RANGE(app, "LaserPointerManager");
_laserPointerManager.update();
}
// Update _viewFrustum with latest camera and view frustum data...
// NOTE: we get this from the view frustum, to make it simpler, since the
// loadViewFrumstum() method will get the correct details from the camera
@ -5515,17 +5644,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
}
renderArgs->_debugFlags = renderDebugFlags;
//ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, transaction);
RenderArgs::OutlineFlags renderOutlineFlags = RenderArgs::RENDER_OUTLINE_NONE;
auto contextOverlayInterface = DependencyManager::get<ContextOverlayInterface>();
if (contextOverlayInterface->getEnabled()) {
if (DependencyManager::get<ContextOverlayInterface>()->getIsInMarketplaceInspectionMode()) {
renderOutlineFlags = RenderArgs::RENDER_OUTLINE_MARKETPLACE_MODE;
} else {
renderOutlineFlags = RenderArgs::RENDER_OUTLINE_WIREFRAMES;
}
}
renderArgs->_outlineFlags = renderOutlineFlags;
}
}
@ -5939,6 +6057,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
scriptEngine->registerGlobalObject("LaserPointers", DependencyManager::get<LaserPointerScriptingInterface>().data());
// Caches
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCache>().data());
@ -5989,6 +6110,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
scriptEngine->registerGlobalObject("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
@ -6224,7 +6346,11 @@ bool Application::askToReplaceDomainContent(const QString& url) {
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
DependencyManager::get<AddressManager>()->handleLookupString(DOMAIN_SPAWNING_POINT);
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
methodDetails = "SuccessfulRequestToReplaceContent";
} else {
methodDetails = "UserDeclinedToReplaceContent";
@ -6237,7 +6363,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
OffscreenUi::warning("Unable to replace content", "You do not have permissions to replace domain content",
QMessageBox::Ok, QMessageBox::Ok);
}
QJsonObject messageProperties = {
QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
@ -6321,7 +6447,14 @@ void Application::showAssetServerWidget(QString filePath) {
void Application::addAssetToWorldFromURL(QString url) {
qInfo(interfaceapp) << "Download model and add to world from" << url;
QString filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
QString filename;
if (url.contains("filename")) {
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
}
if (url.contains("vr.google.com/downloads")) {
filename = url.section('/', -1);
filename.remove(".zip");
}
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
QString errorInfo = "You do not have permissions to write to the Asset Server.";
@ -6342,7 +6475,17 @@ void Application::addAssetToWorldFromURLRequestFinished() {
auto url = request->getUrl().toString();
auto result = request->getResult();
QString filename = url.section("filename=", 1, 1); // Filename from trailing "?filename=" URL parameter.
QString filename;
bool isBlocks = false;
if (url.contains("filename")) {
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
}
if (url.contains("vr.google.com/downloads")) {
filename = url.section('/', -1);
filename.remove(".zip");
isBlocks = true;
}
if (result == ResourceRequest::Success) {
qInfo(interfaceapp) << "Downloaded model from" << url;
@ -6357,7 +6500,8 @@ void Application::addAssetToWorldFromURLRequestFinished() {
if (tempFile.open(QIODevice::WriteOnly)) {
tempFile.write(request->getData());
addAssetToWorldInfoClear(filename); // Remove message from list; next one added will have a different key.
qApp->getFileDownloadInterface()->runUnzip(downloadPath, url, true, false);
tempFile.close();
qApp->getFileDownloadInterface()->runUnzip(downloadPath, url, true, false, isBlocks);
} else {
QString errorInfo = "Couldn't open temporary file for download";
qWarning(interfaceapp) << errorInfo;
@ -6387,7 +6531,7 @@ void Application::addAssetToWorldUnzipFailure(QString filePath) {
addAssetToWorldError(filename, "Couldn't unzip file " + filename + ".");
}
void Application::addAssetToWorld(QString filePath, QString zipFile, bool isZip) {
void Application::addAssetToWorld(QString filePath, QString zipFile, bool isZip, bool isBlocks) {
// Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget().
QString mapping;
QString path = filePath;
@ -6396,6 +6540,11 @@ void Application::addAssetToWorld(QString filePath, QString zipFile, bool isZip)
QString assetFolder = zipFile.section("/", -1);
assetFolder.remove(".zip");
mapping = "/" + assetFolder + "/" + filename;
} else if (isBlocks) {
qCDebug(interfaceapp) << "Path to asset folder: " << zipFile;
QString assetFolder = zipFile.section('/', -1);
assetFolder.remove(".zip?noDownload=false");
mapping = "/" + assetFolder + "/" + filename;
} else {
mapping = "/" + filename;
}
@ -6775,12 +6924,12 @@ void Application::onAssetToWorldMessageBoxClosed() {
}
void Application::handleUnzip(QString zipFile, QStringList unzipFile, bool autoAdd, bool isZip) {
void Application::handleUnzip(QString zipFile, QStringList unzipFile, bool autoAdd, bool isZip, bool isBlocks) {
if (autoAdd) {
if (!unzipFile.isEmpty()) {
for (int i = 0; i < unzipFile.length(); i++) {
qCDebug(interfaceapp) << "Preparing file for asset server: " << unzipFile.at(i);
addAssetToWorld(unzipFile.at(i), zipFile, isZip);
addAssetToWorld(unzipFile.at(i), zipFile, isZip, isBlocks);
}
} else {
addAssetToWorldUnzipFailure(zipFile);
@ -7169,14 +7318,6 @@ void Application::updateDisplayMode() {
qFatal("Attempted to switch display plugins from a non-main thread");
}
// Some plugins *cough* Oculus *cough* process message events from inside their
// display function, and we don't want to change the display plugin underneath
// the paintGL call, so we need to guard against that
// The current oculus runtime doesn't do this anymore
if (_inPaint) {
qFatal("Attempted to switch display plugins while in painting");
}
auto menu = Menu::getInstance();
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
@ -7253,12 +7394,12 @@ void Application::updateDisplayMode() {
bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
auto oldDisplayPlugin = _displayPlugin;
if (_displayPlugin) {
disconnect(_displayPluginPresentConnection);
disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
_displayPlugin->deactivate();
}
auto oldDisplayPlugin = _displayPlugin;
bool active = newDisplayPlugin->activate();
if (!active) {
@ -7296,7 +7437,7 @@ void Application::updateDisplayMode() {
_offscreenContext->makeCurrent();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
_displayPluginPresentConnection = connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
offscreenUi->getDesktop()->setProperty("repositionLocked", wasRepositionLocked);
}
@ -7548,3 +7689,5 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) {
_avatarOverrideUrl = url;
_saveAvatarOverrideUrl = save;
}
#include "Application.moc"

View file

@ -37,7 +37,6 @@
#include <PhysicalEntitySimulation.h>
#include <PhysicsEngine.h>
#include <plugins/Forward.h>
#include <plugins/DisplayPlugin.h>
#include <ui-plugins/PluginContainer.h>
#include <ScriptEngine.h>
#include <ShapeManager.h>
@ -71,6 +70,9 @@
#include "ui/overlays/Overlays.h"
#include "UndoStackScriptingInterface.h"
#include "raypick/RayPickManager.h"
#include "raypick/LaserPointerManager.h"
#include <procedural/ProceduralSkybox.h>
#include <model/Skybox.h>
#include <ModelScriptingInterface.h>
@ -128,12 +130,6 @@ public:
virtual DisplayPluginPointer getActiveDisplayPlugin() const override;
enum Event {
Paint = QEvent::User + 1,
Idle,
Lambda
};
// FIXME? Empty methods, do we still need them?
static void initPlugins(const QStringList& arguments);
static void shutdownPlugins();
@ -302,6 +298,9 @@ public:
QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; }
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
LaserPointerManager& getLaserPointerManager() { return _laserPointerManager; }
RayPickManager& getRayPickManager() { return _rayPickManager; }
signals:
void svoImportRequested(const QString& url);
@ -333,14 +332,14 @@ public slots:
// FIXME: Move addAssetToWorld* methods to own class?
void addAssetToWorldFromURL(QString url);
void addAssetToWorldFromURLRequestFinished();
void addAssetToWorld(QString filePath, QString zipFile, bool isZip);
void addAssetToWorld(QString filePath, QString zipFile, bool isZip, bool isBlocks);
void addAssetToWorldUnzipFailure(QString filePath);
void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy);
void addAssetToWorldUpload(QString filePath, QString mapping);
void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash);
void addAssetToWorldAddEntity(QString filePath, QString mapping);
void handleUnzip(QString sourceFile, QStringList destinationFile, bool autoAdd, bool isZip);
void handleUnzip(QString sourceFile, QStringList destinationFile, bool autoAdd, bool isZip, bool isBlocks);
FileScriptingInterface* getFileDownloadInterface() { return _fileDownload; }
@ -456,10 +455,11 @@ private slots:
private:
static void initDisplay();
void init();
bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event);
bool handleFileOpenEvent(QFileOpenEvent* event);
void cleanupBeforeQuit();
bool shouldPaint();
bool shouldPaint() const;
void idle();
void update(float deltaTime);
@ -542,6 +542,7 @@ private:
QTimer _minimizedWindowTimer;
QElapsedTimer _timerStart;
QElapsedTimer _lastTimeUpdated;
QElapsedTimer _lastTimeRendered;
ShapeManager _shapeManager;
PhysicalEntitySimulationPointer _entitySimulation;
@ -634,7 +635,6 @@ private:
ThreadSafeValueCache<OverlayID> _keyboardFocusedOverlay;
quint64 _lastAcceptedKeyPress = 0;
bool _isForeground = true; // starts out assumed to be in foreground
bool _inPaint = false;
bool _isGLInitialized { false };
bool _physicsEnabled { false };
@ -700,6 +700,11 @@ private:
QUrl _avatarOverrideUrl;
bool _saveAvatarOverrideUrl { false };
QObject* _renderEventHandler{ nullptr };
RayPickManager _rayPickManager;
LaserPointerManager _laserPointerManager;
friend class RenderEventHandler;
};
#endif // hifi_Application_h

View file

@ -63,6 +63,10 @@ void LocationBookmarks::setHomeLocation() {
Bookmarks::addBookmarkToFile(HOME_BOOKMARK, bookmarkAddress);
}
void LocationBookmarks::setHomeLocationToAddress(const QVariant& address) {
Bookmarks::insert("Home", address);
}
void LocationBookmarks::teleportToBookmark() {
QAction* action = qobject_cast<QAction*>(sender());
QString address = action->data().toString();

View file

@ -27,6 +27,7 @@ public:
public slots:
void addBookmark();
void setHomeLocationToAddress(const QVariant& address);
protected:
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& address) override;

View file

@ -56,6 +56,8 @@ Menu* Menu::getInstance() {
return dynamic_cast<Menu*>(qApp->getWindow()->menuBar());
}
const char* exclusionGroupKey = "exclusionGroup";
Menu::Menu() {
auto dialogsManager = DependencyManager::get<DialogsManager>();
auto accountManager = DependencyManager::get<AccountManager>();
@ -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)));

View file

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

View file

@ -473,19 +473,25 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID)
RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude,
const QScriptValue& avatarIdsToDiscard) {
RayToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findRayIntersection",
Q_RETURN_ARG(RayToAvatarIntersectionResult, result),
Q_ARG(const PickRay&, ray),
Q_ARG(const QScriptValue&, avatarIdsToInclude),
Q_ARG(const QScriptValue&, avatarIdsToDiscard));
return result;
}
QVector<EntityItemID> avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude);
QVector<EntityItemID> avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard);
return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard);
}
RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const PickRay& ray,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard) {
RayToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findRayIntersectionVector",
Q_RETURN_ARG(RayToAvatarIntersectionResult, result),
Q_ARG(const PickRay&, ray),
Q_ARG(const QVector<EntityItemID>&, avatarsToInclude),
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard));
return result;
}
glm::vec3 normDirection = glm::normalize(ray.direction);
for (auto avatarData : _avatarHash) {

View file

@ -73,6 +73,9 @@ public:
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
const QScriptValue& avatarIdsToInclude = QScriptValue(),
const QScriptValue& avatarIdsToDiscard = QScriptValue());
RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
// TODO: remove this HACK once we settle on optimal default sort coefficients
Q_INVOKABLE float getAvatarSortCoefficient(const QString& name);

View file

@ -573,6 +573,7 @@ void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("joints");
// copy out the skeleton joints from the model
if (_rigEnabled) {
QWriteLocker writeLock(&_jointDataLock);
_skeletonModel->getRig().copyJointsIntoJointData(_jointData);
}
}
@ -938,6 +939,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)
@ -1087,6 +1091,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();
@ -1209,6 +1216,15 @@ int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) {
return buffer.size();
}
ScriptAvatarData* MyAvatar::getTargetAvatar() const {
auto avatar = std::static_pointer_cast<Avatar>(_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
@ -1237,9 +1253,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.
@ -1256,14 +1271,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<HMDScriptingInterface>();
float ipdScale = hmdInterface->getIPDScale();
@ -1277,7 +1297,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);
@ -1309,7 +1329,7 @@ glm::vec3 MyAvatar::getDefaultEyePosition() const {
const float SCRIPT_PRIORITY = 1.0f + 1.0f;
const float RECORDER_PRIORITY = 1.0f + 1.0f;
void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
void MyAvatar::setJointRotations(const QVector<glm::quat>& jointRotations) {
int numStates = glm::min(_skeletonModel->getJointStateCount(), jointRotations.size());
for (int i = 0; i < numStates; ++i) {
// HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here
@ -1353,6 +1373,50 @@ void MyAvatar::clearJointData(int index) {
_skeletonModel->getRig().clearJointAnimationPriority(index);
}
void MyAvatar::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointData", Q_ARG(QString, name), Q_ARG(const glm::quat&, rotation),
Q_ARG(const glm::vec3&, translation));
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
});
}
void MyAvatar::setJointRotation(const QString& name, const glm::quat& rotation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(QString, name), Q_ARG(const glm::quat&, rotation));
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
});
}
void MyAvatar::setJointTranslation(const QString& name, const glm::vec3& translation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(QString, name), Q_ARG(const glm::vec3&, translation));
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
});
}
void MyAvatar::clearJointData(const QString& name) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(QString, name));
return;
}
writeLockWithNamedJointIndex(name, [&](int index) {
_skeletonModel->getRig().clearJointAnimationPriority(index);
});
}
void MyAvatar::clearJointsData() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearJointsData");
@ -2481,6 +2545,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) {

View file

@ -23,6 +23,7 @@
#include <controllers/Pose.h>
#include <controllers/Actions.h>
#include <avatars-renderer/Avatar.h>
#include <avatars-renderer/ScriptAvatar.h>
#include "AtRestDetector.h"
#include "MyCharacterController.h"
@ -138,6 +139,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)
@ -390,6 +394,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;
@ -414,13 +419,20 @@ public:
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
virtual void setJointRotations(QVector<glm::quat> jointRotations) override;
virtual void setJointRotations(const QVector<glm::quat>& jointRotations) override;
virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) override;
virtual void setJointRotation(int index, const glm::quat& rotation) override;
virtual void setJointTranslation(int index, const glm::vec3& translation) override;
virtual void clearJointData(int index) override;
virtual void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) override;
virtual void setJointRotation(const QString& name, const glm::quat& rotation) override;
virtual void setJointTranslation(const QString& name, const glm::vec3& translation) override;
virtual void clearJointData(const QString& name) override;
virtual void clearJointsData() override;
Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
Q_INVOKABLE bool clearPinOnJoint(int index);

View file

@ -10,72 +10,111 @@
//
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include "AccountManager.h"
#include "Wallet.h"
#include "Ledger.h"
#include "CommerceLogging.h"
// inventory answers {status: 'success', data: {assets: [{id: "guid", title: "name", preview: "url"}....]}}
// balance answers {status: 'success', data: {balance: integer}}
// buy and receive_at answer {status: 'success'}
QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) {
QByteArray response = reply.readAll();
QJsonObject data = QJsonDocument::fromJson(response).object();
qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
return data;
}
// Non-200 responses are not json:
QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) {
QString response = reply.readAll();
qWarning(commerce) << "FAILED" << label << response;
QJsonObject result
{
{ "status", "fail" },
{ "message", response }
};
return result;
}
#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply& reply) { emit NAME##Result(apiResponse(#NAME, reply)); }
#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply& reply) { emit NAME##Result(failResponse(#NAME, reply)); }
#define Handler(NAME) ApiHandler(NAME) FailHandler(NAME)
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<AccountManager>();
const QString URL = "/api/v1/commerce/";
JSONCallbackParameters callbackParams(this, success, this, fail);
qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact);
accountManager->sendRequest(URL + endpoint,
AccountManagerAuth::Required,
method,
callbackParams,
QJsonDocument(request).toJson());
}
void Ledger::signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail) {
auto wallet = DependencyManager::get<Wallet>();
QString signature = key.isEmpty() ? "" : wallet->signWithKey(text, key);
QJsonObject request;
request[propertyName] = QString(text);
request["signature"] = signature;
send(endpoint, success, fail, QNetworkAccessManager::PutOperation, request);
}
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
auto wallet = DependencyManager::get<Wallet>();
QJsonObject request;
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, request);
}
void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) {
QJsonObject transaction;
transaction["hfc_key"] = hfc_key;
transaction["hfc"] = cost;
transaction["cost"] = cost;
transaction["asset_id"] = asset_id;
transaction["inventory_key"] = inventory_key;
transaction["inventory_buyer_username"] = buyerUsername;
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
auto wallet = DependencyManager::get<Wallet>();
QString signature = wallet->signWithKey(transactionString, hfc_key);
QJsonObject request;
request["transaction"] = QString(transactionString);
request["signature"] = signature;
qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact);
// FIXME: talk to server instead
if (_inventory.contains(asset_id)) {
// This is here more for testing than as a definition of semantics.
// When we have popcerts, you will certainly be able to buy a new instance of an item that you already own a different instance of.
// I'm not sure what the server should do for now in this project's MVP.
return emit buyResult("Already owned.");
}
if (initializedBalance() < cost) {
return emit buyResult("Insufficient funds.");
}
_balance -= cost;
QJsonObject inventoryAdditionObject;
inventoryAdditionObject["id"] = asset_id;
inventoryAdditionObject["title"] = "Test Title";
inventoryAdditionObject["preview"] = "https://www.aspca.org/sites/default/files/cat-care_cat-nutrition-tips_overweight_body4_left.jpg";
_inventory.push_back(inventoryAdditionObject);
emit buyResult("");
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure");
}
bool Ledger::receiveAt(const QString& hfc_key) {
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
auto accountManager = DependencyManager::get<AccountManager>();
if (!accountManager->isLoggedIn()) {
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
emit receiveAtResult("Not logged in");
QJsonObject result{ { "status", "fail" }, { "message", "Not logged in" } };
emit receiveAtResult(result);
return false; // We know right away that we will fail, so tell the caller.
}
auto username = accountManager->getAccountInfo().getUsername();
qCInfo(commerce) << "Setting default receiving key for" << username;
emit receiveAtResult(""); // FIXME: talk to server instead.
signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
}
void Ledger::balance(const QStringList& keys) {
// FIXME: talk to server instead
qCInfo(commerce) << "Balance:" << initializedBalance();
emit balanceResult(_balance, "");
keysQuery("balance", "balanceSuccess", "balanceFailure");
}
void Ledger::inventory(const QStringList& keys) {
// FIXME: talk to server instead
QJsonObject inventoryObject;
inventoryObject.insert("success", true);
inventoryObject.insert("assets", _inventory);
qCInfo(commerce) << "Inventory:" << inventoryObject;
emit inventoryResult(inventoryObject, "");
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
}
void Ledger::history(const QStringList& keys) {
keysQuery("history", "historySuccess", "historyFailure");
}
// The api/failResponse is called just for the side effect of logging.
void Ledger::resetSuccess(QNetworkReply& reply) { apiResponse("reset", reply); }
void Ledger::resetFailure(QNetworkReply& reply) { failResponse("reset", reply); }
void Ledger::reset() {
send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, QJsonObject());
}

View file

@ -14,9 +14,10 @@
#ifndef hifi_Ledger_h
#define hifi_Ledger_h
#include <QJsonObject>
#include <DependencyManager.h>
#include <qjsonobject.h>
#include <qjsonarray.h>
#include <QtNetwork/QNetworkReply>
class Ledger : public QObject, public Dependency {
Q_OBJECT
@ -24,21 +25,39 @@ class Ledger : public QObject, public Dependency {
public:
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = "");
bool receiveAt(const QString& hfc_key);
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);
void reset();
signals:
void buyResult(const QString& failureReason);
void receiveAtResult(const QString& failureReason);
void balanceResult(int balance, const QString& failureReason);
void inventoryResult(QJsonObject inventory, const QString& failureReason);
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);
void buyFailure(QNetworkReply& reply);
void receiveAtSuccess(QNetworkReply& reply);
void receiveAtFailure(QNetworkReply& reply);
void balanceSuccess(QNetworkReply& reply);
void balanceFailure(QNetworkReply& reply);
void inventorySuccess(QNetworkReply& reply);
void inventoryFailure(QNetworkReply& reply);
void historySuccess(QNetworkReply& reply);
void historyFailure(QNetworkReply& reply);
void resetSuccess(QNetworkReply& reply);
void resetFailure(QNetworkReply& reply);
private:
// These in-memory caches is temporary, until we start sending things to the server.
int _balance{ -1 };
QJsonArray _inventory{};
int initializedBalance() { if (_balance < 0) _balance = 100; return _balance; }
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
QJsonObject failResponse(const QString& label, QNetworkReply& reply);
void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request);
void keysQuery(const QString& endpoint, const QString& success, const QString& fail);
void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail);
};
#endif // hifi_Ledger_h

View file

@ -1,5 +1,5 @@
//
// Commerce.cpp
// QmlCommerce.cpp
// interface/src/commerce
//
// Created by Howard Stearns on 8/4/17.
@ -14,6 +14,7 @@
#include "DependencyManager.h"
#include "Ledger.h"
#include "Wallet.h"
#include <AccountManager.h>
HIFI_QML_DEF(QmlCommerce)
@ -24,6 +25,8 @@ 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);
}
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
@ -31,14 +34,12 @@ void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUser
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) {
return emit buyResult("Uninitialized Wallet.");
QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } };
return emit buyResult(result);
}
QString key = keys[0];
// For now, we receive at the same key that pays for it.
ledger->buy(key, cost, assetId, key, buyerUsername);
// FIXME: until we start talking to server, report post-transaction balance and inventory so we can see log for testing.
balance();
inventory();
}
void QmlCommerce::balance() {
@ -46,17 +47,48 @@ void QmlCommerce::balance() {
auto wallet = DependencyManager::get<Wallet>();
ledger->balance(wallet->listPublicKeys());
}
void QmlCommerce::inventory() {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
ledger->inventory(wallet->listPublicKeys());
}
void QmlCommerce::chooseSecurityImage(uint imageID) {
void QmlCommerce::history() {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageID);
ledger->history(wallet->listPublicKeys());
}
void QmlCommerce::chooseSecurityImage(const QString& imageFile) {
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageFile);
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getSecurityImage();
}
void QmlCommerce::getLoginStatus() {
emit loginStatusResult(DependencyManager::get<AccountManager>()->isLoggedIn());
}
void QmlCommerce::setPassphrase(const QString& passphrase) {
emit passphraseSetupStatusResult(true);
}
void QmlCommerce::getPassphraseSetupStatus() {
emit passphraseSetupStatusResult(false);
}
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get<Wallet>();
wallet->sendKeyFilePathIfExists();
}
void QmlCommerce::reset() {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
ledger->reset();
wallet->reset();
}

View file

@ -1,5 +1,5 @@
//
// Commerce.h
// QmlCommerce.h
// interface/src/commerce
//
// Guard for safe use of Commerce (Wallet, Ledger) by authorized QML.
@ -15,8 +15,11 @@
#ifndef hifi_QmlCommerce_h
#define hifi_QmlCommerce_h
#include <QJsonObject>
#include <OffscreenQmlDialog.h>
#include <QPixmap>
class QmlCommerce : public OffscreenQmlDialog {
Q_OBJECT
HIFI_QML_DECL
@ -25,19 +28,29 @@ public:
QmlCommerce(QQuickItem* parent = nullptr);
signals:
void buyResult(const QString& failureMessage);
// Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
void buyResult(QJsonObject result);
// Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
void balanceResult(int balance, const QString& failureMessage);
void inventoryResult(QJsonObject inventory, const QString& failureMessage);
void securityImageResult(uint imageID);
void balanceResult(QJsonObject result);
void inventoryResult(QJsonObject result);
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 chooseSecurityImage(uint imageID);
Q_INVOKABLE void history();
Q_INVOKABLE void chooseSecurityImage(const QString& imageFile);
Q_INVOKABLE void getSecurityImage();
Q_INVOKABLE void getLoginStatus();
Q_INVOKABLE void setPassphrase(const QString& passphrase);
Q_INVOKABLE void getPassphraseSetupStatus();
Q_INVOKABLE void getKeyFilePathIfExists();
Q_INVOKABLE void reset();
};
#endif // hifi_QmlCommerce_h

View file

@ -12,10 +12,16 @@
#include "CommerceLogging.h"
#include "Ledger.h"
#include "Wallet.h"
#include "Application.h"
#include "ui/ImageProvider.h"
#include "scripting/HMDScriptingInterface.h"
#include <PathUtils.h>
#include <OffscreenUi.h>
#include <QFile>
#include <QCryptographicHash>
#include <QQmlContext>
#include <openssl/ssl.h>
#include <openssl/err.h>
@ -23,8 +29,10 @@
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
static const char* KEY_FILE = "hifikey";
static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile
void initialize() {
static bool initialized = false;
@ -40,18 +48,30 @@ QString keyFilePath() {
return PathUtils::getAppDataFilePath(KEY_FILE);
}
// for now the callback function just returns the same string. Later we can hook
// this to the gui (some thought required)
QString imageFilePath() {
return PathUtils::getAppDataFilePath(IMAGE_FILE);
}
// use the cached _passphrase if it exists, otherwise we need to prompt
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
// just return a hardcoded pwd for now
static const char* pwd = "pwd";
strcpy(password, pwd);
return static_cast<int>(strlen(pwd));
auto passphrase = DependencyManager::get<Wallet>()->getPassphrase();
if (passphrase) {
strcpy(password, passphrase->toLocal8Bit().constData());
return static_cast<int>(passphrase->size());
} else {
// ok gotta bring up modal dialog... But right now lets just
// just keep it empty
return 0;
}
}
// BEGIN copied code - this will be removed/changed at some point soon
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
//
// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably
// we should just return a list of public keys?
QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
RSA* keyPair = RSA_new();
@ -106,9 +126,11 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
// now lets persist them to files
// TODO: figure out a scheme for multiple keys, etc...
// FIXME: for now I'm appending to the file if it exists. As long as we always put
// the keys in the same order, this works fine. TODO: verify this will skip over
// anything else (like an embedded image)
FILE* fp;
if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) {
if ((fp = fopen(keyFilePath().toStdString().c_str(), "at"))) {
if (!PEM_write_RSAPublicKey(fp, keyPair)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
@ -125,7 +147,9 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
RSA_free(keyPair);
// prepare the return values
// prepare the return values. TODO: Fix this - we probably don't really even want the
// private key at all (better to read it when we need it?). Or maybe we do, when we have
// multiple keys?
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength ),
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength );
@ -192,6 +216,131 @@ RSA* readPrivateKey(const char* filename) {
return key;
}
static const unsigned char IVEC[16] = "IAmAnIVecYay123";
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
// first ivec
memcpy(ivec, IVEC, 16);
auto hash = QCryptographicHash::hash(salt, QCryptographicHash::Sha256);
memcpy(ckey, hash.data(), 32);
}
Wallet::~Wallet() {
if (_securityImage) {
delete _securityImage;
}
}
void Wallet::setPassphrase(const QString& passphrase) {
if (_passphrase) {
delete _passphrase;
}
_passphrase = new QString(passphrase);
}
// encrypt some stuff
bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) {
// aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just
// use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be
// a constant. We can review this later - there are ways to generate keys
// from a password that may be better.
unsigned char ivec[16];
unsigned char ckey[32];
initializeAESKeys(ivec, ckey, _salt);
int tempSize, outSize;
// read entire unencrypted file into memory
QFile inputFile(inputFilePath);
if (!inputFile.exists()) {
qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist";
return false;
}
inputFile.open(QIODevice::ReadOnly);
QByteArray inputFileBuffer = inputFile.readAll();
inputFile.close();
// reserve enough capacity for encrypted bytes
unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE];
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
// TODO: add error handling!!!
if (!EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) {
qCDebug(commerce) << "encrypt init failure";
delete[] outputFileBuffer;
return false;
}
if (!EVP_EncryptUpdate(ctx, outputFileBuffer, &tempSize, (unsigned char*)inputFileBuffer.data(), inputFileBuffer.size())) {
qCDebug(commerce) << "encrypt update failure";
delete[] outputFileBuffer;
return false;
}
outSize = tempSize;
if (!EVP_EncryptFinal_ex(ctx, outputFileBuffer + outSize, &tempSize)) {
qCDebug(commerce) << "encrypt final failure";
delete[] outputFileBuffer;
return false;
}
outSize += tempSize;
EVP_CIPHER_CTX_free(ctx);
qCDebug(commerce) << "encrypted buffer size" << outSize;
QByteArray output((const char*)outputFileBuffer, outSize);
QFile outputFile(outputFilePath);
outputFile.open(QIODevice::WriteOnly);
outputFile.write(output);
outputFile.close();
delete[] outputFileBuffer;
return true;
}
bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) {
unsigned char ivec[16];
unsigned char ckey[32];
initializeAESKeys(ivec, ckey, _salt);
// read encrypted file
QFile inputFile(inputFilePath);
if (!inputFile.exists()) {
qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist";
return false;
}
inputFile.open(QIODevice::ReadOnly);
QByteArray encryptedBuffer = inputFile.readAll();
inputFile.close();
// setup decrypted buffer
unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()];
int tempSize;
// TODO: add error handling
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) {
qCDebug(commerce) << "decrypt init failure";
delete[] outputBuffer;
return false;
}
if (!EVP_DecryptUpdate(ctx, outputBuffer, &tempSize, (unsigned char*)encryptedBuffer.data(), encryptedBuffer.size())) {
qCDebug(commerce) << "decrypt update failure";
delete[] outputBuffer;
return false;
}
*outputBufferSize = tempSize;
if (!EVP_DecryptFinal_ex(ctx, outputBuffer + tempSize, &tempSize)) {
qCDebug(commerce) << "decrypt final failure";
delete[] outputBuffer;
return false;
}
EVP_CIPHER_CTX_free(ctx);
*outputBufferSize += tempSize;
*outputBufferPtr = outputBuffer;
qCDebug(commerce) << "decrypted buffer size" << *outputBufferSize;
return true;
}
bool Wallet::createIfNeeded() {
if (_publicKeys.count() > 0) return false;
@ -205,7 +354,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;
}
}
@ -216,17 +365,20 @@ bool Wallet::createIfNeeded() {
bool Wallet::generateKeyPair() {
qCInfo(commerce) << "Generating keypair.";
auto keyPair = generateRSAKeypair();
_publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64()));
qCDebug(commerce) << "public key:" << _publicKeys.last();
sendKeyFilePathIfExists();
QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last();
QString key = keyPair.first->toBase64();
_publicKeys.push_back(key);
qCDebug(commerce) << "public key:" << key;
// It's arguable whether we want to change the receiveAt every time, but:
// 1. It's certainly needed the first time, when createIfNeeded answers true.
// 2. It is maximally private, and we can step back from that later if desired.
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
auto ledger = DependencyManager::get<Ledger>();
return ledger->receiveAt(_publicKeys.last());
return ledger->receiveAt(key, oldKey);
}
QStringList Wallet::listPublicKeys() {
qCInfo(commerce) << "Enumerating public keys.";
createIfNeeded();
@ -260,17 +412,103 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
RSA_free(rsaPrivateKey);
if (encryptReturn != -1) {
return QUrl::toPercentEncoding(signature.toBase64());
return signature.toBase64();
}
}
return QString();
}
void Wallet::updateImageProvider() {
// inform the image provider. Note it doesn't matter which one you inform, as the
// images are statics
auto engine = DependencyManager::get<OffscreenUi>()->getSurfaceContext()->engine();
auto imageProvider = reinterpret_cast<ImageProvider*>(engine->imageProvider(ImageProvider::PROVIDER_NAME));
imageProvider->setSecurityImage(_securityImage);
}
void Wallet::chooseSecurityImage(uint imageID) {
_chosenSecurityImage = (SecurityImage)imageID;
emit securityImageResult(imageID);
void Wallet::chooseSecurityImage(const QString& filename) {
if (_securityImage) {
delete _securityImage;
}
// temporary...
QString path = qApp->applicationDirPath();
path.append("/resources/qml/hifi/commerce/wallet/");
path.append(filename);
// now create a new security image pixmap
_securityImage = new QPixmap();
qCDebug(commerce) << "loading data for pixmap from" << path;
_securityImage->load(path);
// encrypt it and save.
if (encryptFile(path, imageFilePath())) {
qCDebug(commerce) << "emitting pixmap";
updateImageProvider();
emit securityImageResult(true);
} else {
qCDebug(commerce) << "failed to encrypt security image";
emit securityImageResult(false);
}
}
void Wallet::getSecurityImage() {
emit securityImageResult(_chosenSecurityImage);
unsigned char* data;
int dataLen;
// if already decrypted, don't do it again
if (_securityImage) {
emit securityImageResult(true);
return;
}
// decrypt and return
QString filePath(imageFilePath());
QFileInfo fileInfo(filePath);
if (fileInfo.exists() && decryptFile(filePath, &data, &dataLen)) {
// create the pixmap
_securityImage = new QPixmap();
_securityImage->loadFromData(data, dataLen, "jpg");
qCDebug(commerce) << "created pixmap from encrypted file";
updateImageProvider();
delete[] data;
emit securityImageResult(true);
} else {
qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)";
emit securityImageResult(false);
}
}
void Wallet::sendKeyFilePathIfExists() {
QString filePath(keyFilePath());
QFileInfo fileInfo(filePath);
if (fileInfo.exists()) {
emit keyFilePathIfExistsResult(filePath);
} else {
emit keyFilePathIfExistsResult("");
}
}
void Wallet::reset() {
_publicKeys.clear();
delete _securityImage;
_securityImage = nullptr;
// tell the provider we got nothing
updateImageProvider();
delete _passphrase;
// for now we need to maintain the hard-coded passphrase.
// FIXME: remove this line as part of wiring up the passphrase
// and probably set it to nullptr
_passphrase = new QString("pwd");
QFile keyFile(keyFilePath());
QFile imageFile(imageFilePath());
keyFile.remove();
imageFile.remove();
}

View file

@ -16,39 +16,45 @@
#include <DependencyManager.h>
#include <QPixmap>
class Wallet : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
~Wallet();
// These are currently blocking calls, although they might take a moment.
bool createIfNeeded();
bool generateKeyPair();
QStringList listPublicKeys();
QString signWithKey(const QByteArray& text, const QString& key);
void chooseSecurityImage(uint imageID);
void chooseSecurityImage(const QString& imageFile);
void getSecurityImage();
void sendKeyFilePathIfExists();
void setSalt(const QByteArray& salt) { _salt = salt; }
QByteArray getSalt() { return _salt; }
void setPassphrase(const QString& passphrase);
QString* getPassphrase() { return _passphrase; }
void reset();
signals:
void securityImageResult(uint imageID);
protected:
// ALWAYS add SecurityImage enum values to the END of the enum.
// They must be in the same order as the images are listed in
// SecurityImageSelection.qml
enum SecurityImage {
NONE = 0,
Cat,
Car,
Dog,
Stars,
Plane,
Gingerbread
};
void securityImageResult(bool exists) ;
void keyFilePathIfExistsResult(const QString& path);
private:
QStringList _publicKeys{};
SecurityImage _chosenSecurityImage = SecurityImage::NONE;
QPixmap* _securityImage { nullptr };
QByteArray _salt {"iamsalt!"};
QString* _passphrase { new QString("pwd") };
void updateImageProvider();
bool encryptFile(const QString& inputFilePath, const QString& outputFilePath);
bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
};
#endif // hifi_Wallet_h

View file

@ -216,7 +216,12 @@ int main(int argc, const char* argv[]) {
SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater);
}
Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted);
// Extend argv to enable WebGL rendering
std::vector<const char*> argvExtended(&argv[0], &argv[argc]);
argvExtended.push_back("--ignore-gpu-blacklist");
int argcExtended = (int)argvExtended.size();
Application app(argcExtended, const_cast<char**>(argvExtended.data()), startupTime, runningMarkerExisted);
// If we failed the OpenGLVersion check, log it.
if (override) {

View file

@ -0,0 +1,48 @@
//
// JointRayPick.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "JointRayPick.h"
#include "DependencyManager.h"
#include "avatar/AvatarManager.h"
JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled) :
RayPick(filter, maxDistance, enabled),
_jointName(jointName),
_posOffset(posOffset),
_dirOffset(dirOffset)
{
}
const PickRay JointRayPick::getPickRay(bool& valid) const {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
bool useAvatarHead = _jointName == "Avatar";
const int INVALID_JOINT = -1;
if (jointIndex != INVALID_JOINT || useAvatarHead) {
glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex);
glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex);
glm::vec3 avatarPos = myAvatar->getPosition();
glm::quat avatarRot = myAvatar->getOrientation();
glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos);
glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot;
// Apply offset
pos = pos + (rot * _posOffset);
glm::vec3 dir = rot * glm::normalize(_dirOffset);
valid = true;
return PickRay(pos, dir);
}
valid = false;
return PickRay();
}

View file

@ -0,0 +1,30 @@
//
// JointRayPick.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_JointRayPick_h
#define hifi_JointRayPick_h
#include "RayPick.h"
class JointRayPick : public RayPick {
public:
JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getPickRay(bool& valid) const override;
private:
std::string _jointName;
glm::vec3 _posOffset;
glm::vec3 _dirOffset;
};
#endif // hifi_JointRayPick_h

View file

@ -0,0 +1,221 @@
//
// LaserPointer.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "LaserPointer.h"
#include "Application.h"
#include "ui/overlays/Overlay.h"
#include "avatar/AvatarManager.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) :
_renderingEnabled(enabled),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_centerEndY(centerEndY),
_lockEnd(lockEnd)
{
_rayPickUID = DependencyManager::get<RayPickScriptingInterface>()->createRayPick(rayProps);
for (auto& state : _renderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second);
}
}
for (auto& state : _defaultRenderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second.second);
}
}
}
LaserPointer::~LaserPointer() {
DependencyManager::get<RayPickScriptingInterface>()->removeRayPick(_rayPickUID);
for (auto& renderState : _renderStates) {
renderState.second.deleteOverlays();
}
for (auto& renderState : _defaultRenderStates) {
renderState.second.second.deleteOverlays();
}
}
void LaserPointer::enable() {
DependencyManager::get<RayPickScriptingInterface>()->enableRayPick(_rayPickUID);
_renderingEnabled = true;
}
void LaserPointer::disable() {
DependencyManager::get<RayPickScriptingInterface>()->disableRayPick(_rayPickUID);
_renderingEnabled = false;
if (!_currentRenderState.empty()) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
}
void LaserPointer::setRenderState(const std::string& state) {
if (!_currentRenderState.empty() && state != _currentRenderState) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
_currentRenderState = state;
}
void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
updateRenderStateOverlay(_renderStates[state].getStartID(), startProps);
updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps);
updateRenderStateOverlay(_renderStates[state].getEndID(), endProps);
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
qApp->getOverlays().editOverlay(id, props);
}
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState) {
PickRay pickRay = qApp->getRayPickManager().getPickRay(_rayPickUID);
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec;
if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) {
endVec = pickRay.origin + pickRay.direction * distance;
} else {
if (!_objectLockEnd.first.isNull()) {
glm::vec3 pos;
glm::quat rot;
glm::vec3 dim;
glm::vec3 registrationPoint;
if (_objectLockEnd.second) {
pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value);
rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value);
dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value);
registrationPoint = glm::vec3(0.5f);
} else {
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_objectLockEnd.first);
pos = props.getPosition();
rot = props.getRotation();
dim = props.getDimensions();
registrationPoint = props.getRegistrationPoint();
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
} else {
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(objectID)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(objectID, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(objectID)->getPosition();
}
}
}
QVariant end = vec3toVariant(endVec);
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("start", vec3toVariant(pickRay.origin));
pathProps.insert("end", end);
pathProps.insert("visible", true);
pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
if (_centerEndY) {
endProps.insert("position", end);
} else {
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
endProps.insert("position", vec3toVariant(endVec + glm::vec3(0, 0.5f * dim.y, 0)));
}
if (_faceAvatar) {
glm::quat rotation = glm::inverse(glm::quat_cast(glm::lookAt(endVec, DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), Vectors::UP)));
endProps.insert("rotation", quatToVariant(glm::quat(glm::radians(glm::vec3(0, glm::degrees(safeEulerAngles(rotation)).y, 0)))));
}
endProps.insert("visible", true);
endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
}
void LaserPointer::disableRenderState(const RenderState& renderState) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("visible", false);
startProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("visible", false);
pathProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
endProps.insert("visible", false);
endProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
}
void LaserPointer::update() {
RayPickResult prevRayPickResult = DependencyManager::get<RayPickScriptingInterface>()->getPrevRayPickResult(_rayPickUID);
if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && prevRayPickResult.type != IntersectionType::NONE) {
updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, prevRayPickResult.distance, prevRayPickResult.objectID, false);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_renderingEnabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), true);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
_startID(startID), _pathID(pathID), _endID(endID)
{
if (!_startID.isNull()) {
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
}
if (!_pathID.isNull()) {
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
}
if (!_endID.isNull()) {
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
}
}
void RenderState::deleteOverlays() {
if (!_startID.isNull()) {
qApp->getOverlays().deleteOverlay(_startID);
}
if (!_pathID.isNull()) {
qApp->getOverlays().deleteOverlay(_pathID);
}
if (!_endID.isNull()) {
qApp->getOverlays().deleteOverlay(_endID);
}
}

View file

@ -0,0 +1,97 @@
//
// LaserPointer.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_LaserPointer_h
#define hifi_LaserPointer_h
#include <QString>
#include "glm/glm.hpp"
#include <DependencyManager.h>
#include "raypick/RayPickScriptingInterface.h"
class RayPickResult;
class RenderState {
public:
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
const OverlayID& getStartID() const { return _startID; }
const OverlayID& getPathID() const { return _pathID; }
const OverlayID& getEndID() const { return _endID; }
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
void deleteOverlays();
private:
OverlayID _startID;
OverlayID _pathID;
OverlayID _endID;
bool _startIgnoreRays;
bool _pathIgnoreRays;
bool _endIgnoreRays;
};
class LaserPointer {
public:
typedef std::unordered_map<std::string, RenderState> RenderStateMap;
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled);
~LaserPointer();
QUuid getRayUID() { return _rayPickUID; }
void enable();
void disable();
const RayPickResult getPrevRayPickResult() { return DependencyManager::get<RayPickScriptingInterface>()->getPrevRayPickResult(_rayPickUID); }
void setRenderState(const std::string& state);
// You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays.
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps);
void setPrecisionPicking(const bool precisionPicking) { DependencyManager::get<RayPickScriptingInterface>()->setPrecisionPicking(_rayPickUID, precisionPicking); }
void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get<RayPickScriptingInterface>()->setIgnoreEntities(_rayPickUID, ignoreEntities); }
void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get<RayPickScriptingInterface>()->setIncludeEntities(_rayPickUID, includeEntities); }
void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get<RayPickScriptingInterface>()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); }
void setIncludeOverlays(const QScriptValue& includeOverlays) { DependencyManager::get<RayPickScriptingInterface>()->setIncludeOverlays(_rayPickUID, includeOverlays); }
void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { DependencyManager::get<RayPickScriptingInterface>()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); }
void setIncludeAvatars(const QScriptValue& includeAvatars) { DependencyManager::get<RayPickScriptingInterface>()->setIncludeAvatars(_rayPickUID, includeAvatars); }
void setLockEndUUID(QUuid objectID, const bool isOverlay) { _objectLockEnd = std::pair<QUuid, bool>(objectID, isOverlay); }
void update();
private:
bool _renderingEnabled;
std::string _currentRenderState { "" };
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
bool _faceAvatar;
bool _centerEndY;
bool _lockEnd;
std::pair<QUuid, bool> _objectLockEnd { std::pair<QUuid, bool>(QUuid(), false)};
QUuid _rayPickUID;
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState);
void disableRenderState(const RenderState& renderState);
};
#endif // hifi_LaserPointer_h

View file

@ -0,0 +1,162 @@
//
// LaserPointerManager.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "LaserPointerManager.h"
QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) {
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled);
if (!laserPointer->getRayUID().isNull()) {
QWriteLocker lock(&_addLock);
QUuid id = QUuid::createUuid();
_laserPointersToAdd.push(std::pair<QUuid, std::shared_ptr<LaserPointer>>(id, laserPointer));
return id;
}
return QUuid();
}
void LaserPointerManager::removeLaserPointer(const QUuid uid) {
QWriteLocker lock(&_removeLock);
_laserPointersToRemove.push(uid);
}
void LaserPointerManager::enableLaserPointer(const QUuid uid) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->enable();
}
}
void LaserPointerManager::disableLaserPointer(const QUuid uid) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->disable();
}
}
void LaserPointerManager::setRenderState(QUuid uid, const std::string& renderState) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setRenderState(renderState);
}
}
void LaserPointerManager::editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->editRenderState(state, startProps, pathProps, endProps);
}
}
const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QReadLocker laserLock(_laserPointerLocks[uid].get());
return _laserPointers[uid]->getPrevRayPickResult();
}
return RayPickResult();
}
void LaserPointerManager::update() {
for (QUuid& uid : _laserPointers.keys()) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
QReadLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->update();
}
QWriteLocker containsLock(&_containsLock);
{
QWriteLocker lock(&_addLock);
while (!_laserPointersToAdd.empty()) {
std::pair<QUuid, std::shared_ptr<LaserPointer>> laserPointerToAdd = _laserPointersToAdd.front();
_laserPointersToAdd.pop();
_laserPointers[laserPointerToAdd.first] = laserPointerToAdd.second;
_laserPointerLocks[laserPointerToAdd.first] = std::make_shared<QReadWriteLock>();
}
}
{
QWriteLocker lock(&_removeLock);
while (!_laserPointersToRemove.empty()) {
QUuid uid = _laserPointersToRemove.front();
_laserPointersToRemove.pop();
_laserPointers.remove(uid);
_laserPointerLocks.remove(uid);
}
}
}
void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setPrecisionPicking(precisionPicking);
}
}
void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setIgnoreEntities(ignoreEntities);
}
}
void LaserPointerManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setIncludeEntities(includeEntities);
}
}
void LaserPointerManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setIgnoreOverlays(ignoreOverlays);
}
}
void LaserPointerManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setIncludeOverlays(includeOverlays);
}
}
void LaserPointerManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setIgnoreAvatars(ignoreAvatars);
}
}
void LaserPointerManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setIncludeAvatars(includeAvatars);
}
}
void LaserPointerManager::setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) {
QReadLocker lock(&_containsLock);
if (_laserPointers.contains(uid)) {
QWriteLocker laserLock(_laserPointerLocks[uid].get());
_laserPointers[uid]->setLockEndUUID(objectID, isOverlay);
}
}

View file

@ -0,0 +1,57 @@
//
// LaserPointerManager.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_LaserPointerManager_h
#define hifi_LaserPointerManager_h
#include <memory>
#include <glm/glm.hpp>
#include <QReadWriteLock>
#include "LaserPointer.h"
class RayPickResult;
class LaserPointerManager {
public:
QUuid createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled);
void removeLaserPointer(const QUuid uid);
void enableLaserPointer(const QUuid uid);
void disableLaserPointer(const QUuid uid);
void setRenderState(QUuid uid, const std::string& renderState);
void editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps);
const RayPickResult getPrevRayPickResult(const QUuid uid);
void setPrecisionPicking(QUuid uid, const bool precisionPicking);
void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities);
void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities);
void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays);
void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays);
void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars);
void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars);
void setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay);
void update();
private:
QHash<QUuid, std::shared_ptr<LaserPointer>> _laserPointers;
QHash<QUuid, std::shared_ptr<QReadWriteLock>> _laserPointerLocks;
QReadWriteLock _addLock;
std::queue<std::pair<QUuid, std::shared_ptr<LaserPointer>>> _laserPointersToAdd;
QReadWriteLock _removeLock;
std::queue<QUuid> _laserPointersToRemove;
QReadWriteLock _containsLock;
};
#endif // hifi_LaserPointerManager_h

View file

@ -0,0 +1,120 @@
//
// LaserPointerScriptingInterface.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "LaserPointerScriptingInterface.h"
#include <QVariant>
#include "GLMHelpers.h"
QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& properties) {
QVariantMap propertyMap = properties.toMap();
bool faceAvatar = false;
if (propertyMap["faceAvatar"].isValid()) {
faceAvatar = propertyMap["faceAvatar"].toBool();
}
bool centerEndY = true;
if (propertyMap["centerEndY"].isValid()) {
centerEndY = propertyMap["centerEndY"].toBool();
}
bool lockEnd = false;
if (propertyMap["lockEnd"].isValid()) {
lockEnd = propertyMap["lockEnd"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
LaserPointer::RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
renderStates[name] = buildRenderState(renderStateMap);
}
}
}
}
LaserPointer::DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, RenderState>(distance, buildRenderState(renderStateMap));
}
}
}
}
return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled);
}
void LaserPointerScriptingInterface::editRenderState(QUuid uid, const QString& renderState, const QVariant& properties) {
QVariantMap propMap = properties.toMap();
QVariant startProps;
if (propMap["start"].isValid()) {
startProps = propMap["start"];
}
QVariant pathProps;
if (propMap["path"].isValid()) {
pathProps = propMap["path"];
}
QVariant endProps;
if (propMap["end"].isValid()) {
endProps = propMap["end"];
}
qApp->getLaserPointerManager().editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps);
}
const RenderState LaserPointerScriptingInterface::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
if (startMap["type"].isValid()) {
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
}
}
QUuid pathID;
if (propMap["path"].isValid()) {
QVariantMap pathMap = propMap["path"].toMap();
// right now paths must be line3ds
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
}
}
QUuid endID;
if (propMap["end"].isValid()) {
QVariantMap endMap = propMap["end"].toMap();
if (endMap["type"].isValid()) {
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
}
}
return RenderState(startID, pathID, endID);
}

View file

@ -0,0 +1,48 @@
//
// LaserPointerScriptingInterface.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_LaserPointerScriptingInterface_h
#define hifi_LaserPointerScriptingInterface_h
#include <QtCore/QObject>
#include "RegisteredMetaTypes.h"
#include "DependencyManager.h"
#include "Application.h"
class LaserPointerScriptingInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public slots:
Q_INVOKABLE QUuid createLaserPointer(const QVariant& properties);
Q_INVOKABLE void enableLaserPointer(QUuid uid) { qApp->getLaserPointerManager().enableLaserPointer(uid); }
Q_INVOKABLE void disableLaserPointer(QUuid uid) { qApp->getLaserPointerManager().disableLaserPointer(uid); }
Q_INVOKABLE void removeLaserPointer(QUuid uid) { qApp->getLaserPointerManager().removeLaserPointer(uid); }
Q_INVOKABLE void editRenderState(QUuid uid, const QString& renderState, const QVariant& properties);
Q_INVOKABLE void setRenderState(QUuid uid, const QString& renderState) { qApp->getLaserPointerManager().setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid) { return qApp->getLaserPointerManager().getPrevRayPickResult(uid); }
Q_INVOKABLE void setPrecisionPicking(QUuid uid, const bool precisionPicking) { qApp->getLaserPointerManager().setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { qApp->getLaserPointerManager().setIgnoreEntities(uid, ignoreEntities); }
Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { qApp->getLaserPointerManager().setIncludeEntities(uid, includeEntities); }
Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { qApp->getLaserPointerManager().setIgnoreOverlays(uid, ignoreOverlays); }
Q_INVOKABLE void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { qApp->getLaserPointerManager().setIncludeOverlays(uid, includeOverlays); }
Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { qApp->getLaserPointerManager().setIgnoreAvatars(uid, ignoreAvatars); }
Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { qApp->getLaserPointerManager().setIncludeAvatars(uid, includeAvatars); }
Q_INVOKABLE void setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); }
private:
const RenderState buildRenderState(const QVariantMap& propMap);
};
#endif // hifi_LaserPointerScriptingInterface_h

View file

@ -0,0 +1,32 @@
//
// MouseRayPick.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/19/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MouseRayPick.h"
#include "DependencyManager.h"
#include "Application.h"
#include "display-plugins/CompositorHelper.h"
MouseRayPick::MouseRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) :
RayPick(filter, maxDistance, enabled)
{
}
const PickRay MouseRayPick::getPickRay(bool& valid) const {
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
if (position.isValid()) {
QVariantMap posMap = position.toMap();
valid = true;
return qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat());
}
valid = false;
return PickRay();
}

View file

@ -0,0 +1,24 @@
//
// MouseRayPick.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/19/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MouseRayPick_h
#define hifi_MouseRayPick_h
#include "RayPick.h"
class MouseRayPick : public RayPick {
public:
MouseRayPick(const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getPickRay(bool& valid) const override;
};
#endif // hifi_MouseRayPick_h

View file

@ -0,0 +1,18 @@
//
// RayPick.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RayPick.h"
RayPick::RayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) :
_filter(filter),
_maxDistance(maxDistance),
_enabled(enabled)
{
}

View file

@ -0,0 +1,138 @@
//
// RayPick.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RayPick_h
#define hifi_RayPick_h
#include <stdint.h>
#include "RegisteredMetaTypes.h"
#include "EntityItemID.h"
#include "ui/overlays/Overlay.h"
class RayPickFilter {
public:
enum FlagBit {
PICK_NOTHING = 0,
PICK_ENTITIES,
PICK_OVERLAYS,
PICK_AVATARS,
PICK_HUD,
PICK_COURSE, // if not set, does precise intersection, otherwise, doesn't
PICK_INCLUDE_INVISIBLE, // if not set, will not intersect invisible elements, otherwise, intersects both visible and invisible elements
PICK_INCLUDE_NONCOLLIDABLE, // if not set, will not intersect noncollidable elements, otherwise, intersects both collidable and noncollidable elements
// NOT YET IMPLEMENTED
PICK_ALL_INTERSECTIONS, // if not set, returns closest intersection, otherwise, returns list of all intersections
NUM_FLAGS, // Not a valid flag
};
typedef std::bitset<NUM_FLAGS> Flags;
// The key is the Flags
Flags _flags;
RayPickFilter() : _flags(getBitMask(PICK_NOTHING)) {}
RayPickFilter(const Flags& flags) : _flags(flags) {}
bool operator== (const RayPickFilter& rhs) const { return _flags == rhs._flags; }
bool operator!= (const RayPickFilter& rhs) const { return _flags != rhs._flags; }
void setFlag(FlagBit flag, bool value) { _flags[flag] = value; }
bool doesPickNothing() const { return _flags[PICK_NOTHING]; }
bool doesPickEntities() const { return _flags[PICK_ENTITIES]; }
bool doesPickOverlays() const { return _flags[PICK_OVERLAYS]; }
bool doesPickAvatars() const { return _flags[PICK_AVATARS]; }
bool doesPickHUD() const { return _flags[PICK_HUD]; }
bool doesPickCourse() const { return _flags[PICK_COURSE]; }
bool doesPickInvisible() const { return _flags[PICK_INCLUDE_INVISIBLE]; }
bool doesPickNonCollidable() const { return _flags[PICK_INCLUDE_NONCOLLIDABLE]; }
bool doesWantAllIntersections() const { return _flags[PICK_ALL_INTERSECTIONS]; }
// Helpers for RayPickManager
Flags getEntityFlags() const {
unsigned int toReturn = getBitMask(PICK_ENTITIES);
if (doesPickInvisible()) {
toReturn |= getBitMask(PICK_INCLUDE_INVISIBLE);
}
if (doesPickNonCollidable()) {
toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE);
}
return Flags(toReturn);
}
Flags getOverlayFlags() const {
unsigned int toReturn = getBitMask(PICK_OVERLAYS);
if (doesPickInvisible()) {
toReturn |= getBitMask(PICK_INCLUDE_INVISIBLE);
}
if (doesPickNonCollidable()) {
toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE);
}
return Flags(toReturn);
}
Flags getAvatarFlags() const { return Flags(getBitMask(PICK_AVATARS)); }
Flags getHUDFlags() const { return Flags(getBitMask(PICK_HUD)); }
static unsigned int getBitMask(FlagBit bit) { return 1 << bit; }
};
class RayPick {
public:
RayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled);
virtual const PickRay getPickRay(bool& valid) const = 0;
void enable() { _enabled = true; }
void disable() { _enabled = false; }
const RayPickFilter& getFilter() { return _filter; }
float getMaxDistance() { return _maxDistance; }
bool isEnabled() { return _enabled; }
const RayPickResult& getPrevRayPickResult() { return _prevResult; }
void setPrecisionPicking(bool precisionPicking) { _filter.setFlag(RayPickFilter::PICK_COURSE, !precisionPicking); }
void setRayPickResult(const RayPickResult& rayPickResult) { _prevResult = rayPickResult; }
const QVector<EntityItemID>& getIgnoreEntites() { return _ignoreEntities; }
const QVector<EntityItemID>& getIncludeEntites() { return _includeEntities; }
const QVector<OverlayID>& getIgnoreOverlays() { return _ignoreOverlays; }
const QVector<OverlayID>& getIncludeOverlays() { return _includeOverlays; }
const QVector<EntityItemID>& getIgnoreAvatars() { return _ignoreAvatars; }
const QVector<EntityItemID>& getIncludeAvatars() { return _includeAvatars; }
void setIgnoreEntities(const QScriptValue& ignoreEntities) { _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); }
void setIncludeEntities(const QScriptValue& includeEntities) { _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); }
void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); }
void setIncludeOverlays(const QScriptValue& includeOverlays) { _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); }
void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); }
void setIncludeAvatars(const QScriptValue& includeAvatars) { _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); }
private:
RayPickFilter _filter;
float _maxDistance;
bool _enabled;
RayPickResult _prevResult;
QVector<EntityItemID> _ignoreEntities;
QVector<EntityItemID> _includeEntities;
QVector<OverlayID> _ignoreOverlays;
QVector<OverlayID> _includeOverlays;
QVector<EntityItemID> _ignoreAvatars;
QVector<EntityItemID> _includeAvatars;
};
#endif // hifi_RayPick_h

View file

@ -0,0 +1,263 @@
//
// RayPickManager.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RayPickManager.h"
#include "Application.h"
#include "EntityScriptingInterface.h"
#include "ui/overlays/Overlays.h"
#include "avatar/AvatarManager.h"
#include "scripting/HMDScriptingInterface.h"
#include "DependencyManager.h"
#include "JointRayPick.h"
#include "StaticRayPick.h"
#include "MouseRayPick.h"
bool RayPickManager::checkAndCompareCachedResults(QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache, RayPickResult& res, const RayPickFilter::Flags& mask) {
if (cache.contains(ray) && cache[ray].find(mask) != cache[ray].end()) {
if (cache[ray][mask].distance < res.distance) {
res = cache[ray][mask];
}
return true;
}
return false;
}
void RayPickManager::cacheResult(const bool intersects, const RayPickResult& resTemp, const RayPickFilter::Flags& mask, RayPickResult& res, QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache) {
if (intersects) {
cache[ray][mask] = resTemp;
if (resTemp.distance < res.distance) {
res = resTemp;
}
} else {
cache[ray][mask] = RayPickResult();
}
}
void RayPickManager::update() {
RayPickCache results;
for (auto& uid : _rayPicks.keys()) {
std::shared_ptr<RayPick> rayPick = _rayPicks[uid];
if (!rayPick->isEnabled() || rayPick->getFilter().doesPickNothing() || rayPick->getMaxDistance() < 0.0f) {
continue;
}
bool valid;
PickRay ray = rayPick->getPickRay(valid);
if (!valid) {
continue;
}
QPair<glm::vec3, glm::vec3> rayKey = QPair<glm::vec3, glm::vec3>(ray.origin, ray.direction);
RayPickResult res;
if (rayPick->getFilter().doesPickEntities()) {
RayToEntityIntersectionResult entityRes;
bool fromCache = true;
bool invisible = rayPick->getFilter().doesPickInvisible();
bool nonCollidable = rayPick->getFilter().doesPickNonCollidable();
RayPickFilter::Flags entityMask = rayPick->getFilter().getEntityFlags();
if (!checkAndCompareCachedResults(rayKey, results, res, entityMask)) {
entityRes = DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(ray, !rayPick->getFilter().doesPickCourse(),
rayPick->getIncludeEntites(), rayPick->getIgnoreEntites(), !invisible, !nonCollidable);
fromCache = false;
}
if (!fromCache) {
cacheResult(entityRes.intersects, RayPickResult(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, entityRes.surfaceNormal),
entityMask, res, rayKey, results);
}
}
if (rayPick->getFilter().doesPickOverlays()) {
RayToOverlayIntersectionResult overlayRes;
bool fromCache = true;
bool invisible = rayPick->getFilter().doesPickInvisible();
bool nonCollidable = rayPick->getFilter().doesPickNonCollidable();
RayPickFilter::Flags overlayMask = rayPick->getFilter().getOverlayFlags();
if (!checkAndCompareCachedResults(rayKey, results, res, overlayMask)) {
overlayRes = qApp->getOverlays().findRayIntersectionVector(ray, !rayPick->getFilter().doesPickCourse(),
rayPick->getIncludeOverlays(), rayPick->getIgnoreOverlays(), !invisible, !nonCollidable);
fromCache = false;
}
if (!fromCache) {
cacheResult(overlayRes.intersects, RayPickResult(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, overlayRes.surfaceNormal),
overlayMask, res, rayKey, results);
}
}
if (rayPick->getFilter().doesPickAvatars()) {
RayPickFilter::Flags avatarMask = rayPick->getFilter().getAvatarFlags();
if (!checkAndCompareCachedResults(rayKey, results, res, avatarMask)) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(ray, rayPick->getIncludeAvatars(), rayPick->getIgnoreAvatars());
cacheResult(avatarRes.intersects, RayPickResult(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection), avatarMask, res, rayKey, results);
}
}
// Can't intersect with HUD in desktop mode
if (rayPick->getFilter().doesPickHUD() && DependencyManager::get<HMDScriptingInterface>()->isHMDMode()) {
RayPickFilter::Flags hudMask = rayPick->getFilter().getHUDFlags();
if (!checkAndCompareCachedResults(rayKey, results, res, hudMask)) {
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateRayUICollisionPoint(ray.origin, ray.direction);
cacheResult(true, RayPickResult(IntersectionType::HUD, 0, glm::distance(ray.origin, hudRes), hudRes), hudMask, res, rayKey, results);
}
}
QWriteLocker lock(_rayPickLocks[uid].get());
if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) {
rayPick->setRayPickResult(res);
} else {
rayPick->setRayPickResult(RayPickResult());
}
}
QWriteLocker containsLock(&_containsLock);
{
QWriteLocker lock(&_addLock);
while (!_rayPicksToAdd.empty()) {
std::pair<QUuid, std::shared_ptr<RayPick>> rayPickToAdd = _rayPicksToAdd.front();
_rayPicksToAdd.pop();
_rayPicks[rayPickToAdd.first] = rayPickToAdd.second;
_rayPickLocks[rayPickToAdd.first] = std::make_shared<QReadWriteLock>();
}
}
{
QWriteLocker lock(&_removeLock);
while (!_rayPicksToRemove.empty()) {
QUuid uid = _rayPicksToRemove.front();
_rayPicksToRemove.pop();
_rayPicks.remove(uid);
_rayPickLocks.remove(uid);
}
}
}
QUuid RayPickManager::createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled) {
QWriteLocker lock(&_addLock);
QUuid id = QUuid::createUuid();
_rayPicksToAdd.push(std::pair<QUuid, std::shared_ptr<RayPick>>(id, std::make_shared<JointRayPick>(jointName, posOffset, dirOffset, filter, maxDistance, enabled)));
return id;
}
QUuid RayPickManager::createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) {
QWriteLocker lock(&_addLock);
QUuid id = QUuid::createUuid();
_rayPicksToAdd.push(std::pair<QUuid, std::shared_ptr<RayPick>>(id, std::make_shared<MouseRayPick>(filter, maxDistance, enabled)));
return id;
}
QUuid RayPickManager::createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled) {
QWriteLocker lock(&_addLock);
QUuid id = QUuid::createUuid();
_rayPicksToAdd.push(std::pair<QUuid, std::shared_ptr<RayPick>>(id, std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled)));
return id;
}
void RayPickManager::removeRayPick(const QUuid uid) {
QWriteLocker lock(&_removeLock);
_rayPicksToRemove.push(uid);
}
void RayPickManager::enableRayPick(const QUuid uid) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker rayPickLock(_rayPickLocks[uid].get());
_rayPicks[uid]->enable();
}
}
void RayPickManager::disableRayPick(const QUuid uid) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker rayPickLock(_rayPickLocks[uid].get());
_rayPicks[uid]->disable();
}
}
const PickRay RayPickManager::getPickRay(const QUuid uid) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
bool valid;
PickRay pickRay = _rayPicks[uid]->getPickRay(valid);
if (valid) {
return pickRay;
}
}
return PickRay();
}
const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QReadLocker lock(_rayPickLocks[uid].get());
return _rayPicks[uid]->getPrevRayPickResult();
}
return RayPickResult();
}
void RayPickManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setPrecisionPicking(precisionPicking);
}
}
void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setIgnoreEntities(ignoreEntities);
}
}
void RayPickManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setIncludeEntities(includeEntities);
}
}
void RayPickManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setIgnoreOverlays(ignoreOverlays);
}
}
void RayPickManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setIncludeOverlays(includeOverlays);
}
}
void RayPickManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setIgnoreAvatars(ignoreAvatars);
}
}
void RayPickManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) {
QReadLocker containsLock(&_containsLock);
if (_rayPicks.contains(uid)) {
QWriteLocker lock(_rayPickLocks[uid].get());
_rayPicks[uid]->setIncludeAvatars(includeAvatars);
}
}

View file

@ -0,0 +1,65 @@
//
// RayPickManager.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RayPickManager_h
#define hifi_RayPickManager_h
#include "RayPick.h"
#include <memory>
#include <QtCore/QObject>
#include <QReadWriteLock>
#include "RegisteredMetaTypes.h"
#include <unordered_map>
#include <queue>
class RayPickResult;
class RayPickManager {
public:
void update();
const PickRay getPickRay(const QUuid uid);
QUuid createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled);
QUuid createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled);
QUuid createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled);
void removeRayPick(const QUuid uid);
void enableRayPick(const QUuid uid);
void disableRayPick(const QUuid uid);
const RayPickResult getPrevRayPickResult(const QUuid uid);
void setPrecisionPicking(QUuid uid, const bool precisionPicking);
void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities);
void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities);
void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays);
void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays);
void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars);
void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars);
private:
QHash<QUuid, std::shared_ptr<RayPick>> _rayPicks;
QHash<QUuid, std::shared_ptr<QReadWriteLock>> _rayPickLocks;
QReadWriteLock _addLock;
std::queue<std::pair<QUuid, std::shared_ptr<RayPick>>> _rayPicksToAdd;
QReadWriteLock _removeLock;
std::queue<QUuid> _rayPicksToRemove;
QReadWriteLock _containsLock;
typedef QHash<QPair<glm::vec3, glm::vec3>, std::unordered_map<RayPickFilter::Flags, RayPickResult>> RayPickCache;
// Returns true if this ray exists in the cache, and if it does, update res if the cached result is closer
bool checkAndCompareCachedResults(QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache, RayPickResult& res, const RayPickFilter::Flags& mask);
void cacheResult(const bool intersects, const RayPickResult& resTemp, const RayPickFilter::Flags& mask, RayPickResult& res, QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache);
};
#endif // hifi_RayPickManager_h

View file

@ -0,0 +1,111 @@
//
// RayPickScriptingInterface.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 8/15/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RayPickScriptingInterface.h"
#include <QVariant>
#include "GLMHelpers.h"
#include "Application.h"
QUuid RayPickScriptingInterface::createRayPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
}
RayPickFilter filter = RayPickFilter();
if (propMap["filter"].isValid()) {
filter = RayPickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
if (propMap["joint"].isValid()) {
std::string jointName = propMap["joint"].toString().toStdString();
if (jointName != "Mouse") {
// x = upward, y = forward, z = lateral
glm::vec3 posOffset = Vectors::ZERO;
if (propMap["posOffset"].isValid()) {
posOffset = vec3FromVariant(propMap["posOffset"]);
}
glm::vec3 dirOffset = Vectors::UP;
if (propMap["dirOffset"].isValid()) {
dirOffset = vec3FromVariant(propMap["dirOffset"]);
}
return qApp->getRayPickManager().createRayPick(jointName, posOffset, dirOffset, filter, maxDistance, enabled);
} else {
return qApp->getRayPickManager().createRayPick(filter, maxDistance, enabled);
}
} else if (propMap["position"].isValid()) {
glm::vec3 position = vec3FromVariant(propMap["position"]);
glm::vec3 direction = -Vectors::UP;
if (propMap["direction"].isValid()) {
direction = vec3FromVariant(propMap["direction"]);
}
return qApp->getRayPickManager().createRayPick(position, direction, filter, maxDistance, enabled);
}
return QUuid();
}
void RayPickScriptingInterface::enableRayPick(QUuid uid) {
qApp->getRayPickManager().enableRayPick(uid);
}
void RayPickScriptingInterface::disableRayPick(QUuid uid) {
qApp->getRayPickManager().disableRayPick(uid);
}
void RayPickScriptingInterface::removeRayPick(QUuid uid) {
qApp->getRayPickManager().removeRayPick(uid);
}
RayPickResult RayPickScriptingInterface::getPrevRayPickResult(QUuid uid) {
return qApp->getRayPickManager().getPrevRayPickResult(uid);
}
void RayPickScriptingInterface::setPrecisionPicking(QUuid uid, const bool precisionPicking) {
qApp->getRayPickManager().setPrecisionPicking(uid, precisionPicking);
}
void RayPickScriptingInterface::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) {
qApp->getRayPickManager().setIgnoreEntities(uid, ignoreEntities);
}
void RayPickScriptingInterface::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) {
qApp->getRayPickManager().setIncludeEntities(uid, includeEntities);
}
void RayPickScriptingInterface::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) {
qApp->getRayPickManager().setIgnoreOverlays(uid, ignoreOverlays);
}
void RayPickScriptingInterface::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) {
qApp->getRayPickManager().setIncludeOverlays(uid, includeOverlays);
}
void RayPickScriptingInterface::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) {
qApp->getRayPickManager().setIgnoreAvatars(uid, ignoreAvatars);
}
void RayPickScriptingInterface::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) {
qApp->getRayPickManager().setIncludeAvatars(uid, includeAvatars);
}

View file

@ -0,0 +1,70 @@
//
// RayPickScriptingInterface.h
// interface/src/raypick
//
// Created by Sam Gondelman 8/15/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RayPickScriptingInterface_h
#define hifi_RayPickScriptingInterface_h
#include <QtCore/QObject>
#include "RegisteredMetaTypes.h"
#include "DependencyManager.h"
#include "RayPick.h"
class RayPickScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT)
Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT)
Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT)
Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT)
Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT)
Q_PROPERTY(unsigned int PICK_COURSE READ PICK_COURSE CONSTANT)
Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT)
Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT)
Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT)
SINGLETON_DEPENDENCY
public slots:
Q_INVOKABLE QUuid createRayPick(const QVariant& properties);
Q_INVOKABLE void enableRayPick(QUuid uid);
Q_INVOKABLE void disableRayPick(QUuid uid);
Q_INVOKABLE void removeRayPick(QUuid uid);
Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid);
Q_INVOKABLE void setPrecisionPicking(QUuid uid, const bool precisionPicking);
Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities);
Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities);
Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays);
Q_INVOKABLE void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays);
Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars);
Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars);
unsigned int PICK_NOTHING() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_NOTHING); }
unsigned int PICK_ENTITIES() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ENTITIES); }
unsigned int PICK_OVERLAYS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_OVERLAYS); }
unsigned int PICK_AVATARS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_AVATARS); }
unsigned int PICK_HUD() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_HUD); }
unsigned int PICK_COURSE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_COURSE); }
unsigned int PICK_INCLUDE_INVISIBLE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); }
unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); }
unsigned int PICK_ALL_INTERSECTIONS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ALL_INTERSECTIONS); }
unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; }
unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; }
unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; }
unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; }
unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
};
#endif // hifi_RayPickScriptingInterface_h

View file

@ -0,0 +1,22 @@
//
// StaticRayPick.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "StaticRayPick.h"
StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled) :
RayPick(filter, maxDistance, enabled),
_pickRay(position, direction)
{
}
const PickRay StaticRayPick::getPickRay(bool& valid) const {
valid = true;
return _pickRay;
}

View file

@ -0,0 +1,28 @@
//
// StaticRayPick.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_StaticRayPick_h
#define hifi_StaticRayPick_h
#include "RayPick.h"
class StaticRayPick : public RayPick {
public:
StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getPickRay(bool& valid) const override;
private:
PickRay _pickRay;
};
#endif // hifi_StaticRayPick_h

View file

@ -82,6 +82,22 @@ bool HMDScriptingInterface::shouldShowHandControllers() const {
return _showHandControllersCount > 0;
}
void HMDScriptingInterface::activateHMDHandMouse() {
QWriteLocker lock(&_hmdHandMouseLock);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", true);
_hmdHandMouseCount++;
}
void HMDScriptingInterface::deactivateHMDHandMouse() {
QWriteLocker lock(&_hmdHandMouseLock);
_hmdHandMouseCount = std::max(_hmdHandMouseCount - 1, 0);
if (_hmdHandMouseCount == 0) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", false);
}
}
void HMDScriptingInterface::closeTablet() {
_showTablet = false;
}
@ -153,50 +169,6 @@ QString HMDScriptingInterface::preferredAudioOutput() const {
return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
}
bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) {
if (QThread::currentThread() != thread()) {
bool result;
BLOCKING_INVOKE_METHOD(this, "setHandLasers", Q_RETURN_ARG(bool, result),
Q_ARG(int, hands), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction));
return result;
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled);
return qApp->getActiveDisplayPlugin()->setHandLaser(hands,
enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None,
color, direction);
}
bool HMDScriptingInterface::setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction) {
if (QThread::currentThread() != thread()) {
bool result;
BLOCKING_INVOKE_METHOD(this, "setExtraLaser", Q_RETURN_ARG(bool, result),
Q_ARG(glm::vec3, worldStart), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction));
return result;
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled);
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto sensorToWorld = myAvatar->getSensorToWorldMatrix();
auto worldToSensor = glm::inverse(sensorToWorld);
auto sensorStart = ::transformPoint(worldToSensor, worldStart);
auto sensorDirection = ::transformVectorFast(worldToSensor, direction);
return qApp->getActiveDisplayPlugin()->setExtraLaser(enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None,
color, sensorStart, sensorDirection);
}
void HMDScriptingInterface::disableExtraLaser() {
setExtraLaser(vec3(0), false, vec4(0), vec3(0));
}
void HMDScriptingInterface::disableHandLasers(int hands) {
setHandLasers(hands, false, vec4(0), vec3(0));
}
bool HMDScriptingInterface::suppressKeyboard() {
return qApp->getActiveDisplayPlugin()->suppressKeyboard();
}

View file

@ -22,6 +22,7 @@ class QScriptEngine;
#include <DependencyManager.h>
#include <display-plugins/AbstractHMDScriptingInterface.h>
#include <QReadWriteLock>
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
Q_OBJECT
@ -51,12 +52,8 @@ public:
Q_INVOKABLE void requestHideHandControllers();
Q_INVOKABLE bool shouldShowHandControllers() const;
Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction);
Q_INVOKABLE void disableHandLasers(int hands);
Q_INVOKABLE bool setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction);
Q_INVOKABLE void disableExtraLaser();
Q_INVOKABLE void activateHMDHandMouse();
Q_INVOKABLE void deactivateHMDHandMouse();
/// Suppress the activation of any on-screen keyboard so that a script operation will
/// not be interrupted by a keyboard popup
@ -119,6 +116,9 @@ private:
bool getHUDLookAtPosition3D(glm::vec3& result) const;
glm::mat4 getWorldHMDMatrix() const;
std::atomic<int> _showHandControllersCount { 0 };
QReadWriteLock _hmdHandMouseLock;
int _hmdHandMouseCount;
};
#endif // hifi_HMDScriptingInterface_h

View file

@ -0,0 +1,195 @@
//
// SelectionScriptingInterface.cpp
// interface/src/scripting
//
// Created by Zach Fox on 2017-08-22.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SelectionScriptingInterface.h"
#include <QDebug>
#include "Application.h"
GameplayObjects::GameplayObjects() {
}
bool GameplayObjects::addToGameplayObjects(const QUuid& avatarID) {
containsData = true;
_avatarIDs.push_back(avatarID);
return true;
}
bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) {
_avatarIDs.erase(std::remove(_avatarIDs.begin(), _avatarIDs.end(), avatarID), _avatarIDs.end());
return true;
}
bool GameplayObjects::addToGameplayObjects(const EntityItemID& entityID) {
containsData = true;
_entityIDs.push_back(entityID);
return true;
}
bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) {
_entityIDs.erase(std::remove(_entityIDs.begin(), _entityIDs.end(), entityID), _entityIDs.end());
return true;
}
bool GameplayObjects::addToGameplayObjects(const OverlayID& overlayID) {
containsData = true;
_overlayIDs.push_back(overlayID);
return true;
}
bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) {
_overlayIDs.erase(std::remove(_overlayIDs.begin(), _overlayIDs.end(), overlayID), _overlayIDs.end());
return true;
}
SelectionScriptingInterface::SelectionScriptingInterface() {
}
bool SelectionScriptingInterface::addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id) {
if (itemType == "avatar") {
return addToGameplayObjects(listName, (QUuid)id);
} else if (itemType == "entity") {
return addToGameplayObjects(listName, (EntityItemID)id);
} else if (itemType == "overlay") {
return addToGameplayObjects(listName, (OverlayID)id);
}
return false;
}
bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id) {
if (itemType == "avatar") {
return removeFromGameplayObjects(listName, (QUuid)id);
} else if (itemType == "entity") {
return removeFromGameplayObjects(listName, (EntityItemID)id);
} else if (itemType == "overlay") {
return removeFromGameplayObjects(listName, (OverlayID)id);
}
return false;
}
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
currentList.addToGameplayObjects(idToAdd);
_selectedItemsListMap.insert(listName, currentList);
emit selectedItemsListChanged(listName);
return true;
}
template <class T> bool SelectionScriptingInterface::removeFromGameplayObjects(const QString& listName, T idToRemove) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
if (currentList.getContainsData()) {
currentList.removeFromGameplayObjects(idToRemove);
_selectedItemsListMap.insert(listName, currentList);
emit selectedItemsListChanged(listName);
return true;
} else {
return false;
}
}
//
// END HANDLING GENERIC ITEMS
//
GameplayObjects SelectionScriptingInterface::getList(const QString& listName) {
return _selectedItemsListMap.value(listName);
}
void SelectionScriptingInterface::printList(const QString& listName) {
GameplayObjects currentList = _selectedItemsListMap.value(listName);
if (currentList.getContainsData()) {
qDebug() << "Avatar IDs:";
for (auto i : currentList.getAvatarIDs()) {
qDebug() << i << ';';
}
qDebug() << "";
qDebug() << "Entity IDs:";
for (auto j : currentList.getEntityIDs()) {
qDebug() << j << ';';
}
qDebug() << "";
qDebug() << "Overlay IDs:";
for (auto k : currentList.getOverlayIDs()) {
qDebug() << k << ';';
}
qDebug() << "";
} else {
qDebug() << "List named" << listName << "doesn't exist.";
}
}
bool SelectionScriptingInterface::removeListFromMap(const QString& listName) {
if (_selectedItemsListMap.remove(listName)) {
emit selectedItemsListChanged(listName);
return true;
} else {
return false;
}
}
SelectionToSceneHandler::SelectionToSceneHandler() {
}
void SelectionToSceneHandler::initialize(const QString& listName) {
_listName = listName;
}
void SelectionToSceneHandler::selectedItemsListChanged(const QString& listName) {
if (listName == _listName) {
updateSceneFromSelectedList();
}
}
void SelectionToSceneHandler::updateSceneFromSelectedList() {
auto mainScene = qApp->getMain3DScene();
if (mainScene) {
GameplayObjects thisList = DependencyManager::get<SelectionScriptingInterface>()->getList(_listName);
render::Transaction transaction;
render::ItemIDs finalList;
render::ItemID currentID;
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
auto& overlays = qApp->getOverlays();
for (QUuid& currentAvatarID : thisList.getAvatarIDs()) {
auto avatar = std::static_pointer_cast<Avatar>(DependencyManager::get<AvatarManager>()->getAvatarBySessionID(currentAvatarID));
if (avatar) {
currentID = avatar->getRenderItemID();
if (currentID != render::Item::INVALID_ITEM_ID) {
finalList.push_back(currentID);
}
}
}
for (EntityItemID& currentEntityID : thisList.getEntityIDs()) {
currentID = entityTreeRenderer->renderableIdForEntityId(currentEntityID);
if (currentID != render::Item::INVALID_ITEM_ID) {
finalList.push_back(currentID);
}
}
for (OverlayID& currentOverlayID : thisList.getOverlayIDs()) {
auto overlay = overlays.getOverlay(currentOverlayID);
if (overlay != NULL) {
currentID = overlay->getRenderItemID();
if (currentID != render::Item::INVALID_ITEM_ID) {
finalList.push_back(currentID);
}
}
}
render::Selection selection(_listName.toStdString(), finalList);
transaction.resetSelection(selection);
mainScene->enqueueTransaction(transaction);
} else {
qWarning() << "SelectionToSceneHandler::updateRendererSelectedList(), Unexpected null scene, possibly during application shutdown";
}
}

View file

@ -0,0 +1,91 @@
// SelectionScriptingInterface.h
// interface/src/scripting
//
// Created by Zach Fox on 2017-08-22.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SelectionScriptingInterface_h
#define hifi_SelectionScriptingInterface_h
#include <QtCore/QObject>
#include <QtCore/QMap>
#include <DependencyManager.h>
#include <AbstractViewStateInterface.h>
#include "RenderableEntityItem.h"
#include "ui/overlays/Overlay.h"
#include <avatar/AvatarManager.h>
class GameplayObjects {
public:
GameplayObjects();
bool getContainsData() { return containsData; }
std::vector<QUuid> getAvatarIDs() { return _avatarIDs; }
bool addToGameplayObjects(const QUuid& avatarID);
bool removeFromGameplayObjects(const QUuid& avatarID);
std::vector<EntityItemID> getEntityIDs() { return _entityIDs; }
bool addToGameplayObjects(const EntityItemID& entityID);
bool removeFromGameplayObjects(const EntityItemID& entityID);
std::vector<OverlayID> getOverlayIDs() { return _overlayIDs; }
bool addToGameplayObjects(const OverlayID& overlayID);
bool removeFromGameplayObjects(const OverlayID& overlayID);
private:
bool containsData { false };
std::vector<QUuid> _avatarIDs;
std::vector<EntityItemID> _entityIDs;
std::vector<OverlayID> _overlayIDs;
};
class SelectionScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
SelectionScriptingInterface();
GameplayObjects getList(const QString& listName);
Q_INVOKABLE void printList(const QString& listName);
Q_INVOKABLE bool removeListFromMap(const QString& listName);
Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id);
Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id);
signals:
void selectedItemsListChanged(const QString& listName);
private:
QMap<QString, GameplayObjects> _selectedItemsListMap;
template <class T> bool addToGameplayObjects(const QString& listName, T idToAdd);
template <class T> bool removeFromGameplayObjects(const QString& listName, T idToRemove);
};
class SelectionToSceneHandler : public QObject {
Q_OBJECT
public:
SelectionToSceneHandler();
void initialize(const QString& listName);
void updateSceneFromSelectedList();
public slots:
void selectedItemsListChanged(const QString& listName);
private:
QString _listName { "" };
};
#endif // hifi_SelectionScriptingInterface_h

View file

@ -264,6 +264,10 @@ void WindowScriptingInterface::showAssetServer(const QString& upload) {
QMetaObject::invokeMethod(qApp, "showAssetServerWidget", Qt::QueuedConnection, Q_ARG(QString, upload));
}
QString WindowScriptingInterface::checkVersion() {
return QCoreApplication::applicationVersion();
}
int WindowScriptingInterface::getInnerWidth() {
return qApp->getWindow()->geometry().width();
}

View file

@ -58,6 +58,7 @@ public slots:
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void showAssetServer(const QString& upload = "");
QString checkVersion();
void copyToClipboard(const QString& text);
void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f);
void takeSecondaryCameraSnapshot();

View file

@ -26,8 +26,7 @@ Base3DOverlay::Base3DOverlay() :
_isSolid(DEFAULT_IS_SOLID),
_isDashedLine(DEFAULT_IS_DASHED_LINE),
_ignoreRayIntersection(false),
_drawInFront(false),
_isAA(true)
_drawInFront(false)
{
}
@ -39,7 +38,6 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
_isDashedLine(base3DOverlay->_isDashedLine),
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
_drawInFront(base3DOverlay->_drawInFront),
_isAA(base3DOverlay->_isAA),
_isGrabbable(base3DOverlay->_isGrabbable)
{
setTransform(base3DOverlay->getTransform());
@ -191,13 +189,6 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
needRenderItemUpdate = true;
}
auto isAA = properties["isAA"];
if (isAA.isValid()) {
bool value = isAA.toBool();
setIsAA(value);
needRenderItemUpdate = true;
}
// Communicate changes to the renderItem if needed
if (needRenderItemUpdate) {
auto itemID = getRenderItemID();
@ -253,9 +244,6 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
if (property == "parentJointIndex") {
return getParentJointIndex();
}
if (property == "isAA") {
return _isAA;
}
return Overlay::getProperty(property);
}

View file

@ -43,14 +43,11 @@ public:
bool getDrawInFront() const { return _drawInFront; }
bool getIsGrabbable() const { return _isGrabbable; }
virtual bool isAA() const { return _isAA; }
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
void setDrawInFront(bool value) { _drawInFront = value; }
void setIsAA(bool value) { _isAA = value; }
void setIsGrabbable(bool value) { _isGrabbable = value; }
virtual AABox getBounds() const override = 0;
@ -75,7 +72,6 @@ protected:
bool _isDashedLine;
bool _ignoreRayIntersection;
bool _drawInFront;
bool _isAA;
bool _isGrabbable { false };
QString _name;

View file

@ -264,10 +264,10 @@ void Circle3DOverlay::render(RenderArgs* args) {
const render::ShapeKey Circle3DOverlay::getShapeKey() {
auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit();
if (getAlpha() != 1.0f) {
if (isTransparent()) {
builder.withTranslucent();
}
if (!getIsSolid()) {
if (!getIsSolid() || shouldDrawHUDLayer()) {
builder.withUnlit().withDepthBias();
}
return builder.build();

Some files were not shown because too many files have changed in this diff Show more