Merge branch 'master' into 21484
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
180
interface/resources/icons/tablet-icons/wallet-a.svg
Normal file
After Width: | Height: | Size: 52 KiB |
276
interface/resources/icons/tablet-icons/wallet-i.svg
Normal file
After Width: | Height: | Size: 56 KiB |
|
@ -39,7 +39,7 @@ Item {
|
|||
width: parent.width
|
||||
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
|
||||
|
||||
profile: HFTabletWebEngineProfile;
|
||||
profile: HFWebEngineProfile;
|
||||
|
||||
property string userScriptUrl: ""
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ Item {
|
|||
width: parent.width
|
||||
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
|
||||
|
||||
profile: HFTabletWebEngineProfile;
|
||||
profile: HFWebEngineProfile;
|
||||
|
||||
property string userScriptUrl: ""
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -73,7 +73,6 @@ Item {
|
|||
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||
});
|
||||
|
||||
root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)";
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -147,7 +147,7 @@ Rectangle {
|
|||
width: parent.width;
|
||||
height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight
|
||||
|
||||
profile: HFTabletWebEngineProfile;
|
||||
profile: HFWebEngineProfile;
|
||||
|
||||
property string userScriptUrl: ""
|
||||
|
||||
|
|
|
@ -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
|
||||
//
|
||||
}
|
|
@ -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
|
||||
//
|
||||
}
|
|
@ -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
|
||||
//
|
||||
}
|
944
interface/resources/qml/hifi/commerce/checkout/Checkout.qml
Normal 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
|
||||
//
|
||||
}
|
Before Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 61 KiB |
|
@ -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
|
||||
//
|
||||
}
|
505
interface/resources/qml/hifi/commerce/purchases/Purchases.qml
Normal 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
|
||||
//
|
||||
}
|
87
interface/resources/qml/hifi/commerce/wallet/Help.qml
Normal 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
|
||||
//
|
||||
}
|
188
interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml
Normal 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
|
||||
//
|
||||
}
|
127
interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml
Normal 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
|
||||
//
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
335
interface/resources/qml/hifi/commerce/wallet/Security.qml
Normal 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
|
||||
//
|
||||
}
|
|
@ -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 : "");
|
||||
}
|
||||
}
|
|
@ -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
|
||||
//
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
73
interface/resources/qml/hifi/commerce/wallet/SendMoney.qml
Normal 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
|
||||
//
|
||||
}
|
590
interface/resources/qml/hifi/commerce/wallet/Wallet.qml
Normal 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
|
||||
//
|
||||
}
|
330
interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
Normal 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
|
||||
//
|
||||
}
|
|
@ -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
|
||||
//
|
||||
}
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
BIN
interface/resources/qml/hifi/commerce/wallet/images/02.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
BIN
interface/resources/qml/hifi/commerce/wallet/images/04.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
interface/resources/qml/hifi/commerce/wallet/images/05.jpg
Normal file
After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 721 B |
10
interface/resources/qml/hifi/tablet/BlocksWebView.qml
Normal 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"}
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
30
interface/resources/shaders/hmd_ui.frag
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
48
interface/src/raypick/JointRayPick.cpp
Normal 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();
|
||||
}
|
30
interface/src/raypick/JointRayPick.h
Normal 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
|
221
interface/src/raypick/LaserPointer.cpp
Normal 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);
|
||||
}
|
||||
}
|
97
interface/src/raypick/LaserPointer.h
Normal 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
|
162
interface/src/raypick/LaserPointerManager.cpp
Normal 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);
|
||||
}
|
||||
}
|
57
interface/src/raypick/LaserPointerManager.h
Normal 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
|
120
interface/src/raypick/LaserPointerScriptingInterface.cpp
Normal 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);
|
||||
}
|
48
interface/src/raypick/LaserPointerScriptingInterface.h
Normal 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
|
32
interface/src/raypick/MouseRayPick.cpp
Normal 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();
|
||||
}
|
24
interface/src/raypick/MouseRayPick.h
Normal 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
|
18
interface/src/raypick/RayPick.cpp
Normal 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)
|
||||
{
|
||||
}
|
138
interface/src/raypick/RayPick.h
Normal 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
|
263
interface/src/raypick/RayPickManager.cpp
Normal 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);
|
||||
}
|
||||
}
|
65
interface/src/raypick/RayPickManager.h
Normal 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
|
111
interface/src/raypick/RayPickScriptingInterface.cpp
Normal 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);
|
||||
}
|
70
interface/src/raypick/RayPickScriptingInterface.h
Normal 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
|
22
interface/src/raypick/StaticRayPick.cpp
Normal 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;
|
||||
}
|
28
interface/src/raypick/StaticRayPick.h
Normal 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
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
195
interface/src/scripting/SelectionScriptingInterface.cpp
Normal 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";
|
||||
}
|
||||
}
|
91
interface/src/scripting/SelectionScriptingInterface.h
Normal 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
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|