diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml
deleted file mode 100644
index b1120058f9..0000000000
--- a/interface/resources/qml/ToolWindow.qml
+++ /dev/null
@@ -1,256 +0,0 @@
-//
-// ToolWindow.qml
-//
-// Created by Bradley Austin Davis on 12 Jan 2016
-// Copyright 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
-//
-
-import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-import QtWebEngine 1.1
-import QtWebChannel 1.0
-import Qt.labs.settings 1.0
-
-import "windows"
-import "controls-uit"
-import "styles-uit"
-
-
-ScrollingWindow {
- id: toolWindow
- resizable: true
- objectName: "ToolWindow"
- destroyOnCloseButton: false
- destroyOnHidden: false
- closable: true
- shown: false
- title: "Edit"
- property alias tabView: tabView
- implicitWidth: 520; implicitHeight: 695
- minSize: Qt.vector2d(456, 500)
-
- HifiConstants { id: hifi }
-
- onParentChanged: {
- if (parent) {
- x = 120;
- y = 120;
- }
- }
-
- onShownChanged: {
- keyboardEnabled = HMD.active;
- }
-
- Settings {
- category: "ToolWindow.Position"
- property alias x: toolWindow.x
- property alias y: toolWindow.y
- }
-
- Item {
- id: toolWindowTabViewItem
- height: pane.scrollHeight
- width: pane.contentWidth
- anchors.left: parent.left
- anchors.top: parent.top
-
- TabView {
- id: tabView
- width: pane.contentWidth
- // Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
- height: pane.scrollHeight
- property int tabCount: 0
-
- Repeater {
- model: 4
- Tab {
- // Force loading of the content even if the tab is not visible
- // (required for letting the C++ code access the webview)
- active: true
- enabled: false
- property string originalUrl: ""
-
- WebView {
- id: webView
- anchors.fill: parent
- enabled: false
- Component.onCompleted: {
- webChannel.registerObject("eventBridge", eventBridge);
- webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
- }
-
- onEnabledChanged: toolWindow.updateVisiblity()
- }
- }
- }
-
- style: TabViewStyle {
-
- frame: Rectangle { // Background shown before content loads.
- anchors.fill: parent
- color: hifi.colors.baseGray
- }
-
- frameOverlap: 0
-
- tab: Rectangle {
- implicitWidth: text.width
- implicitHeight: 3 * text.height
- color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
-
- RalewayRegular {
- id: text
- text: styleData.title
- font.capitalization: Font.AllUppercase
- size: hifi.fontSizes.tabName
- width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
- elide: Text.ElideRight
- color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
- horizontalAlignment: Text.AlignHCenter
- anchors.centerIn: parent
- }
-
- Rectangle { // Separator.
- width: 1
- height: parent.height
- color: hifi.colors.black
- anchors.left: parent.left
- anchors.top: parent.top
- visible: styleData.index > 0
-
- Rectangle {
- width: 1
- height: 1
- color: hifi.colors.baseGray
- anchors.left: parent.left
- anchors.bottom: parent.bottom
- }
- }
-
- Rectangle { // Active underline.
- width: parent.width - (styleData.index > 0 ? 1 : 0)
- height: 1
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
- }
- }
-
- tabOverlap: 0
- }
- }
- }
-
- function updateVisiblity() {
- if (visible) {
- for (var i = 0; i < tabView.count; ++i) {
- if (tabView.getTab(i).enabled) {
- return;
- }
- }
- shown = false;
- }
- }
-
- function findIndexForUrl(source) {
- for (var i = 0; i < tabView.count; ++i) {
- var tab = tabView.getTab(i);
- if (tab.originalUrl === source) {
- return i;
- }
- }
- return -1;
- }
-
- function findTabForUrl(source) {
- var index = findIndexForUrl(source);
- if (index < 0) {
- return;
- }
- return tabView.getTab(index);
- }
-
- function showTabForUrl(source, newVisible) {
- var index = findIndexForUrl(source);
- if (index < 0) {
- return;
- }
-
- var tab = tabView.getTab(index);
- if (newVisible) {
- toolWindow.shown = true
- tab.enabled = true
- } else {
- tab.enabled = false;
- updateVisiblity();
- }
- }
-
- function findFreeTab() {
- for (var i = 0; i < tabView.count; ++i) {
- var tab = tabView.getTab(i);
- if (tab && (!tab.originalUrl || tab.originalUrl === "")) {
- return i;
- }
- }
- return -1;
- }
-
- function removeTabForUrl(source) {
- var index = findIndexForUrl(source);
- if (index < 0) {
- return;
- }
-
- var tab = tabView.getTab(index);
- tab.title = "";
- tab.enabled = false;
- tab.originalUrl = "";
- tab.item.url = "about:blank";
- tab.item.enabled = false;
- tabView.tabCount--;
- }
-
- function addWebTab(properties) {
- if (!properties.source) {
- console.warn("Attempted to open Web Tool Pane without URL");
- return;
- }
-
- var existingTabIndex = findIndexForUrl(properties.source);
- if (existingTabIndex >= 0) {
- var tab = tabView.getTab(existingTabIndex);
- return tab.item;
- }
-
- var freeTabIndex = findFreeTab();
- if (freeTabIndex === -1) {
- console.warn("Unable to add new tab");
- return;
- }
-
- if (properties.width) {
- tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
- }
-
- if (properties.height) {
- tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y);
- }
-
- var tab = tabView.getTab(freeTabIndex);
- tab.title = properties.title || "Unknown";
- tab.enabled = true;
- tab.originalUrl = properties.source;
-
- var result = tab.item;
- result.enabled = true;
- tabView.tabCount++;
- result.url = properties.source;
- return result;
- }
-}
diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml
index e636bfc27f..b942c8b4ab 100644
--- a/interface/resources/qml/controls-uit/TextField.qml
+++ b/interface/resources/qml/controls-uit/TextField.qml
@@ -27,10 +27,12 @@ TextField {
property bool hasRoundedBorder: false
property bool error: false;
property bool hasClearButton: false;
+ property string leftPlaceholderGlyph: "";
placeholderText: textField.placeholderText
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
+ FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; }
font.family: firaSansSemiBold.name
font.pixelSize: hifi.fontSizes.textFieldInput
font.italic: textField.text == ""
@@ -54,6 +56,7 @@ TextField {
}
style: TextFieldStyle {
+ id: style;
textColor: {
if (isLightColorScheme) {
if (textField.activeFocus) {
@@ -102,6 +105,16 @@ TextField {
border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0
radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0)
+ HiFiGlyphs {
+ text: textField.leftPlaceholderGlyph;
+ color: textColor;
+ size: hifi.fontSizes.textFieldSearchIcon;
+ anchors.left: parent.left;
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.leftMargin: hifi.dimensions.textPadding - 2;
+ visible: text;
+ }
+
HiFiGlyphs {
text: hifi.glyphs.search
color: textColor
@@ -132,7 +145,7 @@ TextField {
placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
- padding.left: (isSearchField ? textField.height - 2 : 0) + hifi.dimensions.textPadding
+ padding.left: ((isSearchField || textField.leftPlaceholderGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml
deleted file mode 100644
index 11a6c99b0c..0000000000
--- a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// 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;
-
- Connections {
- target: 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
- //
-}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index ef2b007dc8..da67569bc3 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -19,8 +19,7 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
import "../common" as HifiCommerceCommon
-
-// references XXX from root context
+import "./sendMoney"
Rectangle {
HifiConstants { id: hifi; }
@@ -316,18 +315,29 @@ Rectangle {
Connections {
onSendSignalToWallet: {
- sendToScript(msg);
+ if (msg.method === 'transactionHistory_usernameLinkClicked') {
+ userInfoViewer.url = msg.usernameLink;
+ userInfoViewer.visible = true;
+ } else {
+ sendToScript(msg);
+ }
}
}
}
SendMoney {
id: sendMoney;
+ z: 997;
visible: root.activeView === "sendMoney";
- anchors.top: titleBarContainer.bottom;
- anchors.bottom: tabButtonsContainer.top;
- anchors.left: parent.left;
- anchors.right: parent.right;
+ anchors.fill: parent;
+ parentAppTitleBarHeight: titleBarContainer.height;
+ parentAppNavBarHeight: tabButtonsContainer.height;
+
+ Connections {
+ onSendSignalToWallet: {
+ sendToScript(msg);
+ }
+ }
}
Security {
@@ -497,7 +507,7 @@ Rectangle {
Rectangle {
id: sendMoneyButtonContainer;
visible: !walletSetup.visible;
- color: hifi.colors.black;
+ color: root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black;
anchors.top: parent.top;
anchors.left: exchangeMoneyButtonContainer.right;
anchors.bottom: parent.bottom;
@@ -513,7 +523,7 @@ Rectangle {
anchors.top: parent.top;
anchors.topMargin: -2;
// Style
- color: hifi.colors.lightGray50;
+ color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight;
}
RalewaySemiBold {
@@ -528,12 +538,24 @@ Rectangle {
anchors.right: parent.right;
anchors.rightMargin: 4;
// Style
- color: hifi.colors.lightGray50;
+ color: root.activeView === "sendMoney" || sendMoneyTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight;
wrapMode: Text.WordWrap;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignTop;
}
+
+ MouseArea {
+ id: sendMoneyTabMouseArea;
+ anchors.fill: parent;
+ hoverEnabled: enabled;
+ onClicked: {
+ root.activeView = "sendMoney";
+ tabButtonsContainer.resetTabButtonColors();
+ }
+ onEntered: parent.color = hifi.colors.blueHighlight;
+ onExited: parent.color = root.activeView === "sendMoney" ? hifi.colors.blueAccent : hifi.colors.black;
+ }
}
// "SECURITY" tab button
@@ -665,9 +687,16 @@ Rectangle {
// TAB BUTTONS END
//
+ HifiControls.TabletWebView {
+ id: userInfoViewer;
+ z: 998;
+ anchors.fill: parent;
+ visible: false;
+ }
+
Item {
id: keyboardContainer;
- z: 998;
+ z: 999;
visible: keyboard.raised;
property bool punctuationMode: false;
anchors {
@@ -713,6 +742,13 @@ Rectangle {
case 'inspectionCertificate_resetCert':
// NOP
break;
+ case 'updateConnections':
+ sendMoney.updateConnections(message.connections);
+ break;
+ case 'selectRecipient':
+ case 'updateSelectedRecipientUsername':
+ sendMoney.fromScript(message);
+ break;
default:
console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
index 780e08caf8..81d38bc0dd 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -19,8 +19,6 @@ import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
-// references XXX from root context
-
Item {
HifiConstants { id: hifi; }
@@ -32,6 +30,20 @@ Item {
property int currentHistoryPage: 1;
property var pagesAlreadyAdded: new Array();
+ onVisibleChanged: {
+ if (visible) {
+ transactionHistoryModel.clear();
+ Commerce.balance();
+ initialHistoryReceived = false;
+ root.currentHistoryPage = 1;
+ root.noMoreHistoryData = false;
+ root.historyRequestPending = true;
+ Commerce.history(root.currentHistoryPage);
+ } else {
+ refreshTimer.stop();
+ }
+ }
+
Connections {
target: Commerce;
@@ -189,20 +201,6 @@ Item {
color: hifi.colors.white;
// Alignment
verticalAlignment: Text.AlignVCenter;
-
- onVisibleChanged: {
- if (visible) {
- transactionHistoryModel.clear();
- Commerce.balance();
- initialHistoryReceived = false;
- root.currentHistoryPage = 1;
- root.noMoreHistoryData = false;
- root.historyRequestPending = true;
- Commerce.history(root.currentHistoryPage);
- } else {
- refreshTimer.stop();
- }
- }
}
// "balance" text below field
@@ -384,8 +382,8 @@ Item {
height: visible ? parent.height : 0;
AnonymousProRegular {
- id: dateText;
- text: model.created_at ? getFormattedDate(model.created_at * 1000) : "";
+ id: hfcText;
+ text: model.hfc_text || '';
// Style
size: 18;
anchors.left: parent.left;
@@ -393,28 +391,33 @@ Item {
anchors.topMargin: 15;
width: 118;
height: paintedHeight;
- color: hifi.colors.blueAccent;
wrapMode: Text.WordWrap;
+ font.bold: true;
// Alignment
horizontalAlignment: Text.AlignRight;
}
AnonymousProRegular {
id: transactionText;
- text: model.text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.text) : model.text) : "";
+ text: model.transaction_text ? (model.status === "invalidated" ? ("INVALIDATED: " + model.transaction_text) : model.transaction_text) : "";
size: 18;
anchors.top: parent.top;
anchors.topMargin: 15;
- anchors.left: dateText.right;
+ anchors.left: hfcText.right;
anchors.leftMargin: 20;
anchors.right: parent.right;
height: paintedHeight;
color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight;
+ linkColor: hifi.colors.blueAccent;
wrapMode: Text.WordWrap;
font.strikeout: model.status === "invalidated";
onLinkActivated: {
- sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
+ if (link.indexOf("users/") !== -1) {
+ sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
+ } else {
+ sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link});
+ }
}
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml
new file mode 100644
index 0000000000..c2d9ef3b19
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml
@@ -0,0 +1,128 @@
+//
+// ConnectionItem.qml
+// qml/hifi/commerce/wallet/sendMoney
+//
+// ConnectionItem
+//
+// Created by Zach Fox on 2018-01-09
+// Copyright 2018 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 QtQuick.Controls.Styles 1.4
+import "../../../../styles-uit"
+import "../../../../controls-uit" as HifiControlsUit
+import "../../../../controls" as HifiControls
+import "../../wallet" as HifiWallet
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+ property bool isSelected: false;
+ property string userName;
+ property string profilePicUrl;
+
+ height: 65;
+ width: parent.width;
+
+ Rectangle {
+ id: mainContainer;
+ // Style
+ color: root.isSelected ? hifi.colors.faintGray : hifi.colors.white;
+ // Size
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.top: parent.top;
+ height: root.height;
+
+ Item {
+ id: avatarImage;
+ visible: profileUrl !== "" && userName !== "";
+ // Size
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: parent.left;
+ anchors.leftMargin: 36;
+ height: root.height - 15;
+ width: visible ? height : 0;
+ clip: true;
+ Image {
+ id: userImage;
+ source: root.profilePicUrl !== "" ? ((0 === root.profilePicUrl.indexOf("http")) ?
+ root.profilePicUrl : (Account.metaverseServerURL + root.profilePicUrl)) : "";
+ mipmap: true;
+ // Anchors
+ anchors.fill: parent
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Item {
+ width: userImage.width;
+ height: userImage.height;
+ Rectangle {
+ anchors.centerIn: parent;
+ width: userImage.width; // This works because userImage is square
+ height: width;
+ radius: width;
+ }
+ }
+ }
+ }
+ AnimatedImage {
+ source: "../../../../../icons/profilePicLoading.gif"
+ anchors.fill: parent;
+ visible: userImage.status != Image.Ready;
+ }
+ }
+
+ RalewaySemiBold {
+ id: userName;
+ anchors.left: avatarImage.right;
+ anchors.leftMargin: 16;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.right: chooseButton.visible ? chooseButton.left : parent.right;
+ anchors.rightMargin: chooseButton.visible ? 10 : 0;
+ // Text size
+ size: 20;
+ // Style
+ color: hifi.colors.baseGray;
+ text: root.userName;
+ elide: Text.ElideRight;
+ // Alignment
+ horizontalAlignment: Text.AlignLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "Choose" button
+ HifiControlsUit.Button {
+ id: chooseButton;
+ visible: root.isSelected;
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.right: parent.right;
+ anchors.rightMargin: 24;
+ height: root.height - 20;
+ width: 110;
+ text: "CHOOSE";
+ onClicked: {
+ var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl };
+ sendToSendMoney(msg);
+ }
+ }
+ }
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ signal sendToSendMoney(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml
new file mode 100644
index 0000000000..267cf0ed51
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml
@@ -0,0 +1,116 @@
+//
+// RecipientDisplay.qml
+// qml/hifi/commerce/wallet/sendMoney
+//
+// RecipientDisplay
+//
+// Created by Zach Fox on 2018-01-11
+// Copyright 2018 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.6
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import "../../../../styles-uit"
+import "../../../../controls-uit" as HifiControlsUit
+import "../../../../controls" as HifiControls
+import "../../common" as HifiCommerceCommon
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ property bool isDisplayingNearby; // as opposed to 'connections'
+ property string displayName;
+ property string userName;
+ property string profilePic;
+
+ Item {
+ visible: root.isDisplayingNearby;
+ anchors.fill: parent;
+
+ RalewaySemiBold {
+ id: recipientDisplayName;
+ text: root.displayName;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.rightMargin: 12;
+ height: parent.height/2;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignBottom;
+ elide: Text.ElideRight;
+ }
+
+ RalewaySemiBold {
+ text: root.userName;
+ // Anchors
+ anchors.bottom: parent.bottom;
+ anchors.left: recipientDisplayName.anchors.left;
+ anchors.leftMargin: recipientDisplayName.anchors.leftMargin;
+ anchors.right: recipientDisplayName.anchors.right;
+ anchors.rightMargin: recipientDisplayName.anchors.rightMargin;
+ height: parent.height/2;
+ // Text size
+ size: 16;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignTop;
+ elide: Text.ElideRight;
+ }
+ }
+
+ Item {
+ visible: !root.isDisplayingNearby;
+ anchors.fill: parent;
+
+ Image {
+ id: userImage;
+ source: root.profilePic;
+ mipmap: true;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: parent.height - 36;
+ width: height;
+ layer.enabled: true;
+ layer.effect: OpacityMask {
+ maskSource: Item {
+ width: userImage.width;
+ height: userImage.height;
+ Rectangle {
+ anchors.centerIn: parent;
+ width: userImage.width; // This works because userImage is square
+ height: width;
+ radius: width;
+ }
+ }
+ }
+ }
+
+ RalewaySemiBold {
+ text: root.userName;
+ // Anchors
+ anchors.left: userImage.right;
+ anchors.leftMargin: 8;
+ anchors.right: parent.right;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: parent.height - 4;
+ // Text size
+ size: 16;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ elide: Text.ElideRight;
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml
new file mode 100644
index 0000000000..9164ba7a8c
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml
@@ -0,0 +1,1502 @@
+//
+// SendMoney.qml
+// qml/hifi/commerce/wallet/sendMoney
+//
+// SendMoney
+//
+// Created by Zach Fox on 2018-01-09
+// Copyright 2018 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.6
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+import "../../../../styles-uit"
+import "../../../../controls-uit" as HifiControlsUit
+import "../../../../controls" as HifiControls
+import "../../common" as HifiCommerceCommon
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ property int parentAppTitleBarHeight;
+ property int parentAppNavBarHeight;
+ property string currentActiveView: "sendMoneyHome";
+ property string nextActiveView: "";
+ property bool isCurrentlyFullScreen: chooseRecipientConnection.visible ||
+ chooseRecipientNearby.visible || sendMoneyStep.visible || paymentSuccess.visible || paymentFailure.visible;
+ property bool isCurrentlySendingMoney: false;
+
+ // This object is always used in a popup or full-screen Wallet section.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup/section.
+ MouseArea {
+ x: 0;
+ y: root.isCurrentlyFullScreen ? 0 : root.parentAppTitleBarHeight;
+ width: parent.width;
+ height: root.isCurrentlyFullScreen ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight;
+ propagateComposedEvents: false;
+ }
+
+ Connections {
+ target: Commerce;
+
+ onBalanceResult : {
+ balanceText.text = result.data.balance;
+ }
+
+ onTransferHfcToNodeResult: {
+ root.isCurrentlySendingMoney = false;
+
+ if (result.status === 'success') {
+ root.nextActiveView = 'paymentSuccess';
+ } else {
+ root.nextActiveView = 'paymentFailure';
+ }
+ }
+
+ onTransferHfcToUsernameResult: {
+ root.isCurrentlySendingMoney = false;
+
+ if (result.status === 'success') {
+ root.nextActiveView = 'paymentSuccess';
+ } else {
+ root.nextActiveView = 'paymentFailure';
+ }
+ }
+ }
+
+ Connections {
+ target: GlobalServices
+ onMyUsernameChanged: {
+ usernameText.text = Account.username;
+ }
+ }
+
+ Component.onCompleted: {
+ Commerce.balance();
+ }
+
+ onNextActiveViewChanged: {
+ if (root.currentActiveView === 'chooseRecipientNearby') {
+ sendSignalToWallet({method: 'disable_ChooseRecipientNearbyMode'});
+ }
+
+ root.currentActiveView = root.nextActiveView;
+
+ if (root.currentActiveView === 'chooseRecipientConnection') {
+ // Refresh connections model
+ connectionsLoading.visible = false;
+ connectionsLoading.visible = true;
+ sendSignalToWallet({method: 'refreshConnections'});
+ } else if (root.currentActiveView === 'sendMoneyHome') {
+ Commerce.balance();
+ } else if (root.currentActiveView === 'chooseRecipientNearby') {
+ sendSignalToWallet({method: 'enable_ChooseRecipientNearbyMode'});
+ }
+ }
+
+ // Send Money Home BEGIN
+ Item {
+ id: sendMoneyHome;
+ visible: root.currentActiveView === "sendMoneyHome";
+ anchors.fill: parent;
+ anchors.topMargin: root.parentAppTitleBarHeight;
+ anchors.bottomMargin: root.parentAppNavBarHeight;
+
+ // Username Text
+ RalewayRegular {
+ id: usernameText;
+ text: Account.username;
+ // Text size
+ size: 24;
+ // Style
+ color: hifi.colors.white;
+ elide: Text.ElideRight;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: parent.width/2;
+ height: 80;
+ }
+
+ // HFC Balance Container
+ Item {
+ id: hfcBalanceContainer;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.right: parent.right;
+ anchors.leftMargin: 20;
+ width: parent.width/2;
+ height: 80;
+
+ // "HFC" balance label
+ HiFiGlyphs {
+ id: balanceLabel;
+ text: hifi.glyphs.hfc;
+ // Size
+ size: 40;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ // Style
+ color: hifi.colors.white;
+ }
+
+ // Balance Text
+ FiraSansRegular {
+ id: balanceText;
+ text: "--";
+ // Text size
+ size: 28;
+ // Anchors
+ anchors.top: balanceLabel.top;
+ anchors.bottom: balanceLabel.bottom;
+ anchors.left: balanceLabel.right;
+ anchors.leftMargin: 10;
+ anchors.right: parent.right;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.white;
+ // Alignment
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "balance" text below field
+ RalewayRegular {
+ text: "BALANCE (HFC)";
+ // Text size
+ size: 14;
+ // Anchors
+ anchors.top: balanceLabel.top;
+ anchors.topMargin: balanceText.paintedHeight + 20;
+ anchors.bottom: balanceLabel.bottom;
+ anchors.left: balanceText.left;
+ anchors.right: balanceText.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.white;
+ }
+ }
+
+ // Send Money
+ Rectangle {
+ id: sendMoneyContainer;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ height: 440;
+
+ RalewaySemiBold {
+ id: sendMoneyText;
+ text: "Send Money To:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 26;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: paintedWidth;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ Item {
+ id: connectionButton;
+ // Anchors
+ anchors.top: sendMoneyText.bottom;
+ anchors.topMargin: 40;
+ anchors.left: parent.left;
+ anchors.leftMargin: 75;
+ height: 95;
+ width: 95;
+
+ Image {
+ anchors.top: parent.top;
+ source: "../images/wallet-bg.jpg";
+ height: 60;
+ width: parent.width;
+ fillMode: Image.PreserveAspectFit;
+ horizontalAlignment: Image.AlignHCenter;
+ verticalAlignment: Image.AlignTop;
+ }
+
+ RalewaySemiBold {
+ text: "Connection";
+ // Anchors
+ anchors.bottom: parent.bottom;
+ height: 15;
+ width: parent.width;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ horizontalAlignment: Text.AlignHCenter;
+ }
+
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: {
+ root.nextActiveView = "chooseRecipientConnection";
+ }
+ }
+ }
+
+ Item {
+ id: nearbyButton;
+ // Anchors
+ anchors.top: sendMoneyText.bottom;
+ anchors.topMargin: connectionButton.anchors.topMargin;
+ anchors.right: parent.right;
+ anchors.rightMargin: connectionButton.anchors.leftMargin;
+ height: connectionButton.height;
+ width: connectionButton.width;
+
+ Image {
+ anchors.top: parent.top;
+ source: "../images/wallet-bg.jpg";
+ height: 60;
+ width: parent.width;
+ fillMode: Image.PreserveAspectFit;
+ horizontalAlignment: Image.AlignHCenter;
+ verticalAlignment: Image.AlignTop;
+ }
+
+ RalewaySemiBold {
+ text: "Someone Nearby";
+ // Anchors
+ anchors.bottom: parent.bottom;
+ height: 15;
+ width: parent.width;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ horizontalAlignment: Text.AlignHCenter;
+ }
+
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: {
+ root.nextActiveView = "chooseRecipientNearby";
+ }
+ }
+ }
+ }
+ }
+ // Send Money Home END
+
+ // Choose Recipient Connection BEGIN
+ Rectangle {
+ id: chooseRecipientConnection;
+ visible: root.currentActiveView === "chooseRecipientConnection";
+ anchors.fill: parent;
+ color: "#AAAAAA";
+
+ ListModel {
+ id: connectionsModel;
+ }
+ ListModel {
+ id: filteredConnectionsModel;
+ }
+
+ Rectangle {
+ anchors.centerIn: parent;
+ width: parent.width - 30;
+ height: parent.height - 30;
+
+ RalewaySemiBold {
+ id: chooseRecipientText_connections;
+ text: "Choose Recipient:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 26;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: paintedWidth;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ HiFiGlyphs {
+ id: closeGlyphButton_connections;
+ text: hifi.glyphs.close;
+ size: 26;
+ anchors.top: parent.top;
+ anchors.topMargin: 10;
+ anchors.right: parent.right;
+ anchors.rightMargin: 10;
+ MouseArea {
+ anchors.fill: parent;
+ hoverEnabled: true;
+ onEntered: {
+ parent.text = hifi.glyphs.closeInverted;
+ }
+ onExited: {
+ parent.text = hifi.glyphs.close;
+ }
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ }
+ }
+ }
+
+ //
+ // FILTER BAR START
+ //
+ Item {
+ id: filterBarContainer;
+ // Size
+ height: 40;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ anchors.top: chooseRecipientText_connections.bottom;
+ anchors.topMargin: 12;
+
+ HifiControlsUit.TextField {
+ id: filterBar;
+ colorScheme: hifi.colorSchemes.faintGray;
+ hasClearButton: true;
+ hasRoundedBorder: true;
+ anchors.fill: parent;
+ placeholderText: "filter recipients";
+
+ onTextChanged: {
+ buildFilteredConnectionsModel();
+ }
+
+ onAccepted: {
+ focus = false;
+ }
+ }
+ }
+ //
+ // FILTER BAR END
+ //
+
+ Item {
+ id: connectionsContainer;
+ anchors.top: filterBarContainer.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+
+ AnimatedImage {
+ id: connectionsLoading;
+ source: "../../../../../icons/profilePicLoading.gif"
+ width: 120;
+ height: width;
+ anchors.top: parent.top;
+ anchors.topMargin: 185;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ }
+
+ ListView {
+ id: connectionsList;
+ ScrollBar.vertical: ScrollBar {
+ policy: connectionsList.contentHeight > parent.parent.height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded;
+ parent: connectionsList.parent;
+ anchors.top: connectionsList.top;
+ anchors.right: connectionsList.right;
+ anchors.bottom: connectionsList.bottom;
+ width: 20;
+ }
+ visible: !connectionsLoading.visible;
+ clip: true;
+ model: filteredConnectionsModel;
+ snapMode: ListView.SnapToItem;
+ // Anchors
+ anchors.fill: parent;
+ delegate: ConnectionItem {
+ isSelected: connectionsList.currentIndex === index;
+ userName: model.userName;
+ profilePicUrl: model.profileUrl;
+ anchors.topMargin: 6;
+ anchors.bottomMargin: 6;
+
+ Connections {
+ onSendToSendMoney: {
+ sendMoneyStep.referrer = "connections";
+ sendMoneyStep.selectedRecipientNodeID = '';
+ sendMoneyStep.selectedRecipientDisplayName = 'connection';
+ sendMoneyStep.selectedRecipientUserName = msg.userName;
+ sendMoneyStep.selectedRecipientProfilePic = msg.profilePicUrl;
+
+ root.nextActiveView = "sendMoneyStep";
+ }
+ }
+
+ MouseArea {
+ enabled: connectionsList.currentIndex !== index;
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ onClicked: {
+ connectionsList.currentIndex = index;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Choose Recipient Connection END
+
+ // Choose Recipient Nearby BEGIN
+ Rectangle {
+ id: chooseRecipientNearby;
+
+ property string selectedRecipient;
+
+ visible: root.currentActiveView === "chooseRecipientNearby";
+ anchors.fill: parent;
+ color: "#AAAAAA";
+
+ Rectangle {
+ anchors.centerIn: parent;
+ width: parent.width - 30;
+ height: parent.height - 30;
+
+ RalewaySemiBold {
+ text: "Choose Recipient:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 26;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: paintedWidth;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ HiFiGlyphs {
+ id: closeGlyphButton_nearby;
+ text: hifi.glyphs.close;
+ size: 26;
+ anchors.top: parent.top;
+ anchors.topMargin: 10;
+ anchors.right: parent.right;
+ anchors.rightMargin: 10;
+ MouseArea {
+ anchors.fill: parent;
+ hoverEnabled: true;
+ onEntered: {
+ parent.text = hifi.glyphs.closeInverted;
+ }
+ onExited: {
+ parent.text = hifi.glyphs.close;
+ }
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ resetSendMoneyData();
+ }
+ }
+ }
+
+ Item {
+ id: selectionInstructionsContainer;
+ visible: chooseRecipientNearby.selectedRecipient === "";
+ anchors.fill: parent;
+
+ RalewaySemiBold {
+ id: selectionInstructions_deselected;
+ text: "Click/trigger on an avatar nearby to select them...";
+ // Anchors
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 200;
+ anchors.left: parent.left;
+ anchors.leftMargin: 58;
+ anchors.right: parent.right;
+ anchors.rightMargin: anchors.leftMargin;
+ height: paintedHeight;
+ // Text size
+ size: 20;
+ // Style
+ color: hifi.colors.baseGray;
+ horizontalAlignment: Text.AlignHCenter;
+ wrapMode: Text.Wrap;
+ }
+ }
+
+ Item {
+ id: selectionMadeContainer;
+ visible: !selectionInstructionsContainer.visible;
+ anchors.fill: parent;
+
+ RalewaySemiBold {
+ id: sendToText;
+ text: "Send To:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 120;
+ anchors.left: parent.left;
+ anchors.leftMargin: 12;
+ width: paintedWidth;
+ height: paintedHeight;
+ // Text size
+ size: 20;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ RalewaySemiBold {
+ id: avatarDisplayName;
+ text: '"' + AvatarList.getAvatar(chooseRecipientNearby.selectedRecipient).sessionDisplayName + '"';
+ // Anchors
+ anchors.top: sendToText.bottom;
+ anchors.topMargin: 60;
+ anchors.left: parent.left;
+ anchors.leftMargin: 30;
+ anchors.right: parent.right;
+ anchors.rightMargin: 30;
+ height: paintedHeight;
+ // Text size
+ size: 22;
+ // Style
+ horizontalAlignment: Text.AlignHCenter;
+ color: hifi.colors.baseGray;
+ }
+
+ RalewaySemiBold {
+ id: avatarNodeID;
+ text: chooseRecipientNearby.selectedRecipient;
+ // Anchors
+ anchors.top: avatarDisplayName.bottom;
+ anchors.topMargin: 6;
+ anchors.left: parent.left;
+ anchors.leftMargin: 30;
+ anchors.right: parent.right;
+ anchors.rightMargin: 30;
+ height: paintedHeight;
+ // Text size
+ size: 14;
+ // Style
+ horizontalAlignment: Text.AlignHCenter;
+ color: hifi.colors.lightGrayText;
+ }
+
+ RalewaySemiBold {
+ id: avatarUserName;
+ text: sendMoneyStep.selectedRecipientUserName;
+ // Anchors
+ anchors.top: avatarNodeID.bottom;
+ anchors.topMargin: 12;
+ anchors.left: parent.left;
+ anchors.leftMargin: 30;
+ anchors.right: parent.right;
+ anchors.rightMargin: 30;
+ height: paintedHeight;
+ // Text size
+ size: 22;
+ // Style
+ horizontalAlignment: Text.AlignHCenter;
+ color: hifi.colors.baseGray;
+ }
+
+ RalewaySemiBold {
+ id: selectionInstructions_selected;
+ text: "Click/trigger on another avatar nearby to select them...\n\nor press 'Next' to continue.";
+ // Anchors
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 200;
+ anchors.left: parent.left;
+ anchors.leftMargin: 58;
+ anchors.right: parent.right;
+ anchors.rightMargin: anchors.leftMargin;
+ height: paintedHeight;
+ // Text size
+ size: 20;
+ // Style
+ color: hifi.colors.baseGray;
+ horizontalAlignment: Text.AlignHCenter;
+ wrapMode: Text.Wrap;
+ }
+ }
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ id: cancelButton;
+ color: hifi.buttons.noneBorderless;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.left: parent.left;
+ anchors.leftMargin: 60;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 80;
+ height: 50;
+ width: 120;
+ text: "Cancel";
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ resetSendMoneyData();
+ }
+ }
+
+ // "Next" button
+ HifiControlsUit.Button {
+ id: nextButton;
+ enabled: chooseRecipientNearby.selectedRecipient !== "";
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.rightMargin: 60;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 80;
+ height: 50;
+ width: 120;
+ text: "Next";
+ onClicked: {
+ sendMoneyStep.referrer = "nearby";
+ sendMoneyStep.selectedRecipientNodeID = chooseRecipientNearby.selectedRecipient;
+ chooseRecipientNearby.selectedRecipient = "";
+
+ root.nextActiveView = "sendMoneyStep";
+ }
+ }
+ }
+ }
+ // Choose Recipient Nearby END
+
+ // Send Money Screen BEGIN
+ Rectangle {
+ id: sendMoneyStep;
+ z: 997;
+
+ property string referrer; // either "connections" or "nearby"
+ property string selectedRecipientNodeID;
+ property string selectedRecipientDisplayName;
+ property string selectedRecipientUserName;
+ property string selectedRecipientProfilePic;
+
+ visible: root.currentActiveView === "sendMoneyStep";
+ anchors.fill: parent;
+ color: "#AAAAAA";
+
+ Rectangle {
+ anchors.centerIn: parent;
+ width: parent.width - 30;
+ height: parent.height - 30;
+
+ RalewaySemiBold {
+ id: sendMoneyText_sendMoneyStep;
+ text: "Send Money To:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 26;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: paintedWidth;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ Item {
+ id: sendToContainer;
+ anchors.top: sendMoneyText_sendMoneyStep.bottom;
+ anchors.topMargin: 20;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ RalewaySemiBold {
+ id: sendToText_sendMoneyStep;
+ text: "Send To:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: 90;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ RecipientDisplay {
+ anchors.top: parent.top;
+ anchors.left: sendToText_sendMoneyStep.right;
+ anchors.right: changeButton.left;
+ anchors.rightMargin: 12;
+ height: parent.height;
+
+ displayName: sendMoneyStep.selectedRecipientDisplayName;
+ userName: sendMoneyStep.selectedRecipientUserName;
+ profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ?
+ sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : "";
+ isDisplayingNearby: sendMoneyStep.referrer === "nearby";
+ }
+
+ // "CHANGE" button
+ HifiControlsUit.Button {
+ id: changeButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: 35;
+ width: 120;
+ text: "CHANGE";
+ onClicked: {
+ if (sendMoneyStep.referrer === "connections") {
+ root.nextActiveView = "chooseRecipientConnection";
+ } else if (sendMoneyStep.referrer === "nearby") {
+ root.nextActiveView = "chooseRecipientNearby";
+ }
+ resetSendMoneyData();
+ }
+ }
+ }
+
+ Item {
+ id: amountContainer;
+ anchors.top: sendToContainer.bottom;
+ anchors.topMargin: 2;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ RalewaySemiBold {
+ id: amountText;
+ text: "Amount:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: 90;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ HifiControlsUit.TextField {
+ id: amountTextField;
+ colorScheme: hifi.colorSchemes.light;
+ inputMethodHints: Qt.ImhDigitsOnly;
+ // Anchors
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: amountText.right;
+ anchors.right: parent.right;
+ height: 50;
+ // Style
+ leftPlaceholderGlyph: hifi.glyphs.hfc;
+ activeFocusOnPress: true;
+ activeFocusOnTab: true;
+
+ validator: IntValidator { bottom: 0; }
+
+ onAccepted: {
+ optionalMessage.focus = true;
+ }
+ }
+
+ RalewaySemiBold {
+ id: amountTextFieldError;
+ // Anchors
+ anchors.top: amountTextField.bottom;
+ anchors.topMargin: 2;
+ anchors.left: amountTextField.left;
+ anchors.right: amountTextField.right;
+ height: 40;
+ // Text size
+ size: 16;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignTop;
+ horizontalAlignment: Text.AlignRight;
+ }
+ }
+
+ Item {
+ id: messageContainer;
+ anchors.top: amountContainer.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 140;
+
+ FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; }
+ TextArea {
+ id: optionalMessage;
+ property int maximumLength: 70;
+ property string previousText: text;
+ placeholderText: "Optional Message";
+ font.family: firaSansSemiBold.name;
+ font.pixelSize: 20;
+ // Anchors
+ anchors.fill: parent;
+ // Style
+ background: Rectangle {
+ anchors.fill: parent;
+ color: optionalMessage.activeFocus ? hifi.colors.white : hifi.colors.textFieldLightBackground;
+ border.width: optionalMessage.activeFocus ? 1 : 0;
+ border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground;
+ }
+ color: hifi.colors.black;
+ textFormat: TextEdit.PlainText;
+ wrapMode: TextEdit.Wrap;
+ activeFocusOnPress: true;
+ activeFocusOnTab: true;
+ // Workaround for no max length on TextAreas
+ onTextChanged: {
+ if (text.length > maximumLength) {
+ var cursor = cursorPosition;
+ text = previousText;
+ if (cursor > text.length) {
+ cursorPosition = text.length;
+ } else {
+ cursorPosition = cursor-1;
+ }
+ }
+ previousText = text;
+ }
+ }
+ RalewaySemiBold {
+ id: optionalMessageCharacterCount;
+ text: optionalMessage.text.length + "/" + optionalMessage.maximumLength;
+ // Anchors
+ anchors.top: optionalMessage.bottom;
+ anchors.topMargin: 2;
+ anchors.right: optionalMessage.right;
+ height: paintedHeight;
+ // Text size
+ size: 16;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignTop;
+ horizontalAlignment: Text.AlignRight;
+ }
+ }
+
+ Item {
+ id: bottomBarContainer;
+ anchors.top: messageContainer.bottom;
+ anchors.topMargin: 30;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ HifiControlsUit.CheckBox {
+ id: sendPubliclyCheckbox;
+ visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN
+ text: "Send Publicly"
+ // Anchors
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: parent.left;
+ anchors.right: cancelButton_sendMoneyStep.left;
+ anchors.rightMargin: 16;
+ boxSize: 24;
+ }
+
+ // "CANCEL" button
+ HifiControlsUit.Button {
+ id: cancelButton_sendMoneyStep;
+ color: hifi.buttons.noneBorderless;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: sendButton.left;
+ anchors.rightMargin: 16;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: 35;
+ width: 100;
+ text: "CANCEL";
+ onClicked: {
+ resetSendMoneyData();
+ root.nextActiveView = "sendMoneyHome";
+ }
+ }
+
+ // "SEND" button
+ HifiControlsUit.Button {
+ id: sendButton;
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: 35;
+ width: 100;
+ text: "SEND";
+ onClicked: {
+ if (parseInt(amountTextField.text) > parseInt(balanceText.text)) {
+ amountTextField.focus = true;
+ amountTextField.error = true;
+ amountTextFieldError.text = "amount exceeds available funds";
+ } else if (amountTextField.text === "" || parseInt(amountTextField.text) < 1) {
+ amountTextField.focus = true;
+ amountTextField.error = true;
+ amountTextFieldError.text = "invalid amount";
+ } else {
+ amountTextFieldError.text = "";
+ amountTextField.error = false;
+ root.isCurrentlySendingMoney = true;
+ amountTextField.focus = false;
+ optionalMessage.focus = false;
+ if (sendMoneyStep.referrer === "connections") {
+ Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text);
+ } else if (sendMoneyStep.referrer === "nearby") {
+ Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Send Money Screen END
+
+ // Sending Money Overlay START
+ Rectangle {
+ id: sendingMoneyOverlay;
+ z: 998;
+
+ visible: root.isCurrentlySendingMoney;
+ anchors.fill: parent;
+ color: Qt.rgba(0.0, 0.0, 0.0, 0.5);
+
+ // This object is always used in a popup or full-screen Wallet section.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup/section.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ }
+
+ AnimatedImage {
+ id: sendingMoneyImage;
+ source: "../../../../../icons/profilePicLoading.gif"
+ width: 160;
+ height: width;
+ anchors.top: parent.top;
+ anchors.topMargin: 185;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ }
+
+ RalewaySemiBold {
+ text: "Sending";
+ // Anchors
+ anchors.top: sendingMoneyImage.bottom;
+ anchors.topMargin: 22;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ width: paintedWidth;
+ // Text size
+ size: 24;
+ // Style
+ color: hifi.colors.white;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+ // Sending Money Overlay END
+
+ // Payment Success BEGIN
+ Rectangle {
+ id: paymentSuccess;
+
+ visible: root.currentActiveView === "paymentSuccess";
+ anchors.fill: parent;
+ color: "#AAAAAA";
+
+ Rectangle {
+ anchors.centerIn: parent;
+ width: parent.width - 30;
+ height: parent.height - 30;
+
+ RalewaySemiBold {
+ id: paymentSentText;
+ text: "Payment Sent";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 26;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: paintedWidth;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ HiFiGlyphs {
+ id: closeGlyphButton_paymentSuccess;
+ text: hifi.glyphs.close;
+ size: 26;
+ anchors.top: parent.top;
+ anchors.topMargin: 10;
+ anchors.right: parent.right;
+ anchors.rightMargin: 10;
+ MouseArea {
+ anchors.fill: parent;
+ hoverEnabled: true;
+ onEntered: {
+ parent.text = hifi.glyphs.closeInverted;
+ }
+ onExited: {
+ parent.text = hifi.glyphs.close;
+ }
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ resetSendMoneyData();
+ }
+ }
+ }
+
+ Item {
+ id: sendToContainer_paymentSuccess;
+ anchors.top: paymentSentText.bottom;
+ anchors.topMargin: 20;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ RalewaySemiBold {
+ id: sendToText_paymentSuccess;
+ text: "Sent To:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: 90;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ RecipientDisplay {
+ anchors.top: parent.top;
+ anchors.left: sendToText_paymentSuccess.right;
+ anchors.right: parent.right;
+ height: parent.height;
+
+ displayName: sendMoneyStep.selectedRecipientDisplayName;
+ userName: sendMoneyStep.selectedRecipientUserName;
+ profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ?
+ sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : "";
+ isDisplayingNearby: sendMoneyStep.referrer === "nearby";
+ }
+ }
+
+ Item {
+ id: amountContainer_paymentSuccess;
+ anchors.top: sendToContainer_paymentSuccess.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ RalewaySemiBold {
+ id: amountText_paymentSuccess;
+ text: "Amount:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: 90;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "HFC" balance label
+ HiFiGlyphs {
+ id: amountSentLabel;
+ text: hifi.glyphs.hfc;
+ // Size
+ size: 32;
+ // Anchors
+ anchors.left: amountText_paymentSuccess.right;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: 50;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ RalewaySemiBold {
+ id: amountSentText;
+ text: amountTextField.text;
+ // Anchors
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: amountSentLabel.right;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ height: 50;
+ // Style
+ size: 22;
+ color: hifi.colors.baseGray;
+ }
+ }
+
+ RalewaySemiBold {
+ id: optionalMessage_paymentSuccess;
+ text: optionalMessage.text;
+ // Anchors
+ anchors.top: amountContainer_paymentSuccess.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 110;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ anchors.bottom: closeButton.top;
+ anchors.bottomMargin: 40;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ wrapMode: Text.Wrap;
+ verticalAlignment: Text.AlignTop;
+ }
+
+ // "Close" button
+ HifiControlsUit.Button {
+ id: closeButton;
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 80;
+ height: 50;
+ width: 120;
+ text: "Close";
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ resetSendMoneyData();
+ }
+ }
+ }
+ }
+ // Payment Success END
+
+ // Payment Failure BEGIN
+ Rectangle {
+ id: paymentFailure;
+
+ visible: root.currentActiveView === "paymentFailure";
+ anchors.fill: parent;
+ color: "#AAAAAA";
+
+ Rectangle {
+ anchors.centerIn: parent;
+ width: parent.width - 30;
+ height: parent.height - 30;
+
+ RalewaySemiBold {
+ id: paymentFailureText;
+ text: "Payment Failed";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 26;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: paintedWidth;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ HiFiGlyphs {
+ id: closeGlyphButton_paymentFailure;
+ text: hifi.glyphs.close;
+ size: 26;
+ anchors.top: parent.top;
+ anchors.topMargin: 10;
+ anchors.right: parent.right;
+ anchors.rightMargin: 10;
+ MouseArea {
+ anchors.fill: parent;
+ hoverEnabled: true;
+ onEntered: {
+ parent.text = hifi.glyphs.closeInverted;
+ }
+ onExited: {
+ parent.text = hifi.glyphs.close;
+ }
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ resetSendMoneyData();
+ }
+ }
+ }
+
+ RalewaySemiBold {
+ id: paymentFailureDetailText;
+ text: "The recipient you specified was unable to receive your payment.";
+ anchors.top: paymentFailureText.bottom;
+ anchors.topMargin: 20;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ wrapMode: Text.Wrap;
+ }
+
+ Item {
+ id: sendToContainer_paymentFailure;
+ anchors.top: paymentFailureDetailText.bottom;
+ anchors.topMargin: 8;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ RalewaySemiBold {
+ id: sentToText_paymentFailure;
+ text: "Sent To:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: 90;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ RecipientDisplay {
+ anchors.top: parent.top;
+ anchors.left: sentToText_paymentFailure.right;
+ anchors.right: parent.right;
+ height: parent.height;
+
+ displayName: sendMoneyStep.selectedRecipientDisplayName;
+ userName: sendMoneyStep.selectedRecipientUserName;
+ profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ?
+ sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : "";
+ isDisplayingNearby: sendMoneyStep.referrer === "nearby";
+ }
+ }
+
+ Item {
+ id: amountContainer_paymentFailure;
+ anchors.top: sendToContainer_paymentFailure.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: 80;
+
+ RalewaySemiBold {
+ id: amountText_paymentFailure;
+ text: "Amount:";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: 90;
+ // Text size
+ size: 18;
+ // Style
+ color: hifi.colors.baseGray;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "HFC" balance label
+ HiFiGlyphs {
+ id: amountSentLabel_paymentFailure;
+ text: hifi.glyphs.hfc;
+ // Size
+ size: 32;
+ // Anchors
+ anchors.left: amountText_paymentFailure.right;
+ anchors.verticalCenter: parent.verticalCenter;
+ height: 50;
+ // Style
+ color: hifi.colors.baseGray;
+ }
+
+ RalewaySemiBold {
+ id: amountSentText_paymentFailure;
+ text: amountTextField.text;
+ // Anchors
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: amountSentLabel_paymentFailure.right;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ height: 50;
+ // Style
+ size: 22;
+ color: hifi.colors.baseGray;
+ }
+ }
+
+ RalewaySemiBold {
+ id: optionalMessage_paymentFailuire;
+ text: optionalMessage.text;
+ // Anchors
+ anchors.top: amountContainer_paymentFailure.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 110;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ anchors.bottom: closeButton_paymentFailure.top;
+ anchors.bottomMargin: 40;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.baseGray;
+ wrapMode: Text.Wrap;
+ verticalAlignment: Text.AlignTop;
+ }
+
+ // "Close" button
+ HifiControlsUit.Button {
+ id: closeButton_paymentFailure;
+ color: hifi.buttons.noneBorderless;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 80;
+ height: 50;
+ width: 120;
+ text: "Cancel";
+ onClicked: {
+ root.nextActiveView = "sendMoneyHome";
+ resetSendMoneyData();
+ }
+ }
+
+ // "Retry" button
+ HifiControlsUit.Button {
+ id: retryButton_paymentFailure;
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.rightMargin: 12;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 80;
+ height: 50;
+ width: 120;
+ text: "Retry";
+ onClicked: {
+ root.isCurrentlySendingMoney = true;
+ if (sendMoneyStep.referrer === "connections") {
+ Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text);
+ } else if (sendMoneyStep.referrer === "nearby") {
+ Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text);
+ }
+ }
+ }
+ }
+ }
+ // Payment Failure END
+
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+
+ function updateConnections(connections) {
+ connectionsModel.clear();
+ connectionsModel.append(connections);
+ buildFilteredConnectionsModel();
+ connectionsLoading.visible = false;
+ }
+
+ function buildFilteredConnectionsModel() {
+ filteredConnectionsModel.clear();
+ for (var i = 0; i < connectionsModel.count; i++) {
+ if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
+ filteredConnectionsModel.append(connectionsModel.get(i));
+ }
+ }
+ }
+
+ function resetSendMoneyData() {
+ amountTextField.focus = false;
+ optionalMessage.focus = false;
+ amountTextFieldError.text = "";
+ amountTextField.error = false;
+ chooseRecipientNearby.selectedRecipient = "";
+ sendMoneyStep.selectedRecipientNodeID = "";
+ sendMoneyStep.selectedRecipientDisplayName = "";
+ sendMoneyStep.selectedRecipientUserName = "";
+ sendMoneyStep.selectedRecipientProfilePic = "";
+ amountTextField.text = "";
+ optionalMessage.text = "";
+ }
+
+ //
+ // 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) {
+ case 'selectRecipient':
+ if (message.isSelected) {
+ chooseRecipientNearby.selectedRecipient = message.id[0];
+ sendMoneyStep.selectedRecipientDisplayName = message.displayName;
+ sendMoneyStep.selectedRecipientUserName = message.userName;
+ } else {
+ chooseRecipientNearby.selectedRecipient = "";
+ sendMoneyStep.selectedRecipientDisplayName = '';
+ sendMoneyStep.selectedRecipientUserName = '';
+ }
+ break;
+ case 'updateSelectedRecipientUsername':
+ sendMoneyStep.selectedRecipientUserName = message.userName;
+ break;
+ default:
+ console.log('SendMoney: Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index bb4e9af2f7..7ba93675a8 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2351,7 +2351,7 @@ void Application::initializeUi() {
QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" },
QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" },
- QUrl{ "hifi/commerce/wallet/SendMoney.qml" },
+ QUrl{ "hifi/commerce/wallet/sendMoney/SendMoney.qml" },
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
QUrl{ "hifi/commerce/wallet/WalletSetup.qml" },
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 3072ecf240..2a33ee6f7b 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -574,8 +574,6 @@ Menu::Menu() {
avatar.get(), SLOT(setEnableMeshVisible(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false);
- addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true,
- avatar.get(), SLOT(setUseAnimPreAndPostRotations(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true,
avatar.get(), SLOT(setEnableInverseKinematics(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false,
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 854f8d8c2b..11a27ff7f5 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -194,7 +194,6 @@ namespace MenuOption {
const QString TurnWithHead = "Turn using Head";
const QString UseAudioForMouth = "Use Audio for Mouth";
const QString UseCamera = "Use Camera";
- const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations";
const QString VelocityFilter = "Velocity Filter";
const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends";
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index e45228bc6f..c9ae9e5d6a 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -1062,11 +1062,6 @@ void MyAvatar::setEnableMeshVisible(bool isEnabled) {
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene());
}
-void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) {
- AnimClip::usePreAndPostPoseFromAnim = isEnabled;
- reset(true);
-}
-
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
_skeletonModel->getRig().setEnableInverseKinematics(isEnabled);
}
@@ -1930,7 +1925,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
_prevShouldDrawHead = shouldDrawHead;
}
-const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f;
+const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f;
bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const {
return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getModelScale());
@@ -2101,7 +2096,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
_actionMotorVelocity = motorSpeed * direction;
} else {
// we're interacting with a floor --> simple horizontal speed and exponential decay
- _actionMotorVelocity = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_WALKING_SPEED * direction;
+ _actionMotorVelocity = getSensorToWorldScale() * _walkSpeed.get() * direction;
}
float boomChange = getDriveKey(ZOOM);
@@ -2693,6 +2688,14 @@ float MyAvatar::getUserEyeHeight() const {
return userHeight - userHeight * ratio;
}
+float MyAvatar::getWalkSpeed() const {
+ return _walkSpeed.get();
+}
+
+void MyAvatar::setWalkSpeed(float value) {
+ _walkSpeed.set(value);
+}
+
glm::vec3 MyAvatar::getPositionForAudio() {
switch (_audioListenerMode) {
case AudioListenerMode::FROM_HEAD:
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index ab74460d4e..58b49b61ff 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -163,6 +163,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT)
+ Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed);
+
const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right";
@@ -557,6 +559,9 @@ public:
const QUuid& getSelfID() const { return AVATAR_SELF_ID; }
+ void setWalkSpeed(float value);
+ float getWalkSpeed() const;
+
public slots:
void increaseSize();
void decreaseSize();
@@ -594,7 +599,6 @@ public slots:
bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
void setEnableMeshVisible(bool isEnabled);
- void setUseAnimPreAndPostRotations(bool isEnabled);
void setEnableInverseKinematics(bool isEnabled);
QUrl getAnimGraphOverrideUrl() const; // thread-safe
@@ -841,6 +845,9 @@ private:
// height of user in sensor space, when standing erect.
ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT };
+
+ // max unscaled forward movement speed
+ ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp
index 15830636f0..50ea6629cf 100644
--- a/interface/src/commerce/Ledger.cpp
+++ b/interface/src/commerce/Ledger.cpp
@@ -46,6 +46,8 @@ Handler(buy)
Handler(receiveAt)
Handler(balance)
Handler(inventory)
+Handler(transferHfcToNode)
+Handler(transferHfcToUsername)
void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) {
auto accountManager = DependencyManager::get();
@@ -116,23 +118,60 @@ void Ledger::inventory(const QStringList& keys) {
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
}
-QString amountString(const QString& label, const QString&color, const QJsonValue& moneyValue, const QJsonValue& certsValue) {
- int money = moneyValue.toInt();
- int certs = certsValue.toInt();
- if (money <= 0 && certs <= 0) {
- return QString();
+QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) {
+ int sent = sentValue.toInt();
+ int received = receivedValue.toInt();
+ if (sent <= 0 && received <= 0) {
+ return QString("-");
}
- QString result(QString(" %2").arg(color, label));
- if (money > 0) {
- result += QString(" %1 HFC").arg(money);
- }
- if (certs > 0) {
- if (money > 0) {
- result += QString(",");
+ QString result;
+ if (sent > 0) {
+ result += QString("-%1 HFC").arg(sent);
+ if (received > 0) {
+ result += QString("
");
}
- result += QString((certs == 1) ? " %1 certificate" : " %1 certificates").arg(certs);
}
- return result + QString("");
+ if (received > 0) {
+ result += QString("%1 HFC").arg(received);
+ }
+ return result;
+}
+static const QString USER_PAGE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/users/";
+QString userLink(const QString& username) {
+ if (username.isEmpty()) {
+ return QString("someone");
+ }
+ return QString("%2").arg(USER_PAGE_BASE_URL, username);
+}
+
+QString transactionString(const QJsonObject& valueObject) {
+ int sentCerts = valueObject["sent_certs"].toInt();
+ int receivedCerts = valueObject["received_certs"].toInt();
+ int sent = valueObject["sent_money"].toInt();
+ int dateInteger = valueObject["created_at"].toInt();
+ QString message = valueObject["message"].toString();
+ QDateTime createdAt(QDateTime::fromSecsSinceEpoch(dateInteger, Qt::UTC));
+ QString result;
+
+ if (sentCerts <= 0 && receivedCerts <= 0) {
+ // this is an hfc transfer.
+ if (sent > 0) {
+ QString recipient = userLink(valueObject["recipient_name"].toString());
+ result += QString("Money sent to %1").arg(recipient);
+ } else {
+ QString sender = userLink(valueObject["sender_name"].toString());
+ result += QString("Money from %1").arg(sender);
+ }
+ if (!message.isEmpty()) {
+ result += QString("
with memo: \"%1\"").arg(message);
+ }
+ } else {
+ result += valueObject["message"].toString();
+ }
+ // no matter what we append a smaller date to the bottom of this...
+
+ result += QString("
%1").arg(createdAt.toLocalTime().toString(Qt::DefaultLocaleShortDate));
+ return result;
}
static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
@@ -155,16 +194,13 @@ void Ledger::historySuccess(QNetworkReply& reply) {
// TODO: do this with 0 copies if possible
for (auto it = historyArray.begin(); it != historyArray.end(); it++) {
+ // We have 2 text fields to synthesize, the one on the left is a listing
+ // of the HFC in/out of your wallet. The one on the right contains an explaination
+ // of the transaction. That could be just the memo (if it is a regular purchase), or
+ // more text (plus the optional memo) if an hfc transfer
auto valueObject = (*it).toObject();
- QString sent = amountString("sent", "EA4C5F", valueObject["sent_money"], valueObject["sent_certs"]);
- QString received = amountString("received", "1FC6A6", valueObject["received_money"], valueObject["received_certs"]);
-
- // turns out on my machine, toLocalTime convert to some weird timezone, yet the
- // systemTimeZone is correct. To avoid a strange bug with other's systems too, lets
- // be explicit
- QDateTime createdAt = QDateTime::fromSecsSinceEpoch(valueObject["created_at"].toInt(), Qt::UTC);
- QDateTime localCreatedAt = createdAt.toTimeZone(QTimeZone::systemTimeZone());
- valueObject["text"] = QString("%1%2%3").arg(valueObject["message"].toString(), sent, received);
+ valueObject["hfc_text"] = hfcString(valueObject["sent_money"], valueObject["received_money"]);
+ valueObject["transaction_text"] = transactionString(valueObject);
newHistoryArray.push_back(valueObject);
}
// now copy the rest of the json -- this is inefficient
@@ -198,11 +234,17 @@ void Ledger::accountSuccess(QNetworkReply& reply) {
auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8());
auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8());
auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8());
+ QString remotePublicKey = data["public_key"].toString();
wallet->setSalt(salt);
wallet->setIv(iv);
wallet->setCKey(ckey);
+ QStringList localPublicKeys = wallet->listPublicKeys();
+ if (remotePublicKey.isEmpty() && !localPublicKeys.isEmpty()) {
+ receiveAt(localPublicKeys.first(), "");
+ }
+
// none of the hfc account info should be emitted
emit accountResult(QJsonObject{ {"status", "success"} });
}
@@ -261,3 +303,25 @@ void Ledger::certificateInfo(const QString& certificateId) {
request["certificate_id"] = certificateId;
send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request);
}
+
+void Ledger::transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage) {
+ QJsonObject transaction;
+ transaction["public_key"] = hfc_key;
+ transaction["node_id"] = nodeID;
+ transaction["quantity"] = amount;
+ transaction["message"] = optionalMessage;
+ QJsonDocument transactionDoc{ transaction };
+ auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
+ signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_node", "transferHfcToNodeSuccess", "transferHfcToNodeFailure");
+}
+
+void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage) {
+ QJsonObject transaction;
+ transaction["public_key"] = hfc_key;
+ transaction["username"] = username;
+ transaction["quantity"] = amount;
+ transaction["message"] = optionalMessage;
+ QJsonDocument transactionDoc{ transaction };
+ auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
+ signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure");
+}
diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h
index 318006b645..1b56c893ab 100644
--- a/interface/src/commerce/Ledger.h
+++ b/interface/src/commerce/Ledger.h
@@ -33,6 +33,8 @@ public:
void account();
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
void certificateInfo(const QString& certificateId);
+ void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
+ void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);
enum CertificateStatus {
CERTIFICATE_STATUS_UNKNOWN = 0,
@@ -51,6 +53,8 @@ signals:
void accountResult(QJsonObject result);
void locationUpdateResult(QJsonObject result);
void certificateInfoResult(QJsonObject result);
+ void transferHfcToNodeResult(QJsonObject result);
+ void transferHfcToUsernameResult(QJsonObject result);
void updateCertificateStatus(const QString& certID, uint certStatus);
@@ -71,6 +75,10 @@ public slots:
void updateLocationFailure(QNetworkReply& reply);
void certificateInfoSuccess(QNetworkReply& reply);
void certificateInfoFailure(QNetworkReply& reply);
+ void transferHfcToNodeSuccess(QNetworkReply& reply);
+ void transferHfcToNodeFailure(QNetworkReply& reply);
+ void transferHfcToUsernameSuccess(QNetworkReply& reply);
+ void transferHfcToUsernameFailure(QNetworkReply& reply);
private:
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 58548eb3ef..c9caa393ce 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -29,6 +29,8 @@ QmlCommerce::QmlCommerce() {
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
+ connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
+ connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
auto accountManager = DependencyManager::get();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
@@ -137,3 +139,27 @@ void QmlCommerce::certificateInfo(const QString& certificateId) {
auto ledger = DependencyManager::get();
ledger->certificateInfo(certificateId);
}
+
+void QmlCommerce::transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage) {
+ auto ledger = DependencyManager::get();
+ auto wallet = DependencyManager::get();
+ QStringList keys = wallet->listPublicKeys();
+ if (keys.count() == 0) {
+ QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
+ return emit buyResult(result);
+ }
+ QString key = keys[0];
+ ledger->transferHfcToNode(key, nodeID, amount, optionalMessage);
+}
+
+void QmlCommerce::transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage) {
+ auto ledger = DependencyManager::get();
+ auto wallet = DependencyManager::get();
+ QStringList keys = wallet->listPublicKeys();
+ if (keys.count() == 0) {
+ QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } };
+ return emit buyResult(result);
+ }
+ QString key = keys[0];
+ ledger->transferHfcToUsername(key, username, amount, optionalMessage);
+}
diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h
index c04ede86c4..b261ee6de6 100644
--- a/interface/src/commerce/QmlCommerce.h
+++ b/interface/src/commerce/QmlCommerce.h
@@ -45,6 +45,9 @@ signals:
void updateCertificateStatus(const QString& certID, uint certStatus);
+ void transferHfcToNodeResult(QJsonObject result);
+ void transferHfcToUsernameResult(QJsonObject result);
+
protected:
Q_INVOKABLE void getWalletStatus();
@@ -65,6 +68,9 @@ protected:
Q_INVOKABLE void account();
Q_INVOKABLE void certificateInfo(const QString& certificateId);
+
+ Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
+ Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
};
#endif // hifi_QmlCommerce_h
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index e36b84ac96..e50eb06355 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -411,6 +411,11 @@ int WindowScriptingInterface::getY() {
}
void WindowScriptingInterface::copyToClipboard(const QString& text) {
+ if (QThread::currentThread() != qApp->thread()) {
+ QMetaObject::invokeMethod(this, "copyToClipboard", Q_ARG(QString, text));
+ return;
+ }
+
qDebug() << "Copying";
QApplication::clipboard()->setText(text);
}
diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp
index 1118e21c91..2ead4558fe 100644
--- a/libraries/animation/src/AnimClip.cpp
+++ b/libraries/animation/src/AnimClip.cpp
@@ -13,8 +13,6 @@
#include "AnimationLogging.h"
#include "AnimUtil.h"
-bool AnimClip::usePreAndPostPoseFromAnim = true;
-
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) :
AnimNode(AnimNode::Type::Clip, id),
_startFrame(startFrame),
@@ -138,14 +136,8 @@ void AnimClip::copyFromNetworkAnim() {
if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) {
AnimPose preRot, postRot;
- if (usePreAndPostPoseFromAnim) {
- preRot = animSkeleton.getPreRotationPose(animJoint);
- postRot = animSkeleton.getPostRotationPose(animJoint);
- } else {
- // In order to support Blender, which does not have preRotation FBX support, we use the models defaultPose as the reference frame for the animations.
- preRot = AnimPose(glm::vec3(1.0f), _skeleton->getRelativeBindPose(skeletonJoint).rot(), glm::vec3());
- postRot = AnimPose::identity;
- }
+ preRot = animSkeleton.getPreRotationPose(animJoint);
+ postRot = animSkeleton.getPostRotationPose(animJoint);
// cancel out scale
preRot.scale() = glm::vec3(1.0f);
diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h
index c7e7ebf3ee..717972ca26 100644
--- a/libraries/animation/src/AnimClip.h
+++ b/libraries/animation/src/AnimClip.h
@@ -25,8 +25,6 @@ class AnimClip : public AnimNode {
public:
friend class AnimTests;
- static bool usePreAndPostPoseFromAnim;
-
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag);
virtual ~AnimClip() override;
diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp
index c8388c3b12..e9f9f8818f 100644
--- a/libraries/animation/src/AnimInverseKinematics.cpp
+++ b/libraries/animation/src/AnimInverseKinematics.cpp
@@ -1255,7 +1255,7 @@ void AnimInverseKinematics::initConstraints() {
// / /
// O--O O--O
- loadDefaultPoses(_skeleton->getRelativeBindPoses());
+ loadDefaultPoses(_skeleton->getRelativeDefaultPoses());
int numJoints = (int)_defaultRelativePoses.size();
diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp
index 070949ab3b..46b3cf1c28 100644
--- a/libraries/animation/src/AnimManipulator.cpp
+++ b/libraries/animation/src/AnimManipulator.cpp
@@ -33,7 +33,7 @@ AnimManipulator::~AnimManipulator() {
}
const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
- return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeBindPoses());
+ return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeDefaultPoses());
}
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp
index 804ffb0583..2bce16c5ca 100644
--- a/libraries/animation/src/AnimSkeleton.cpp
+++ b/libraries/animation/src/AnimSkeleton.cpp
@@ -56,14 +56,6 @@ int AnimSkeleton::getChainDepth(int jointIndex) const {
}
}
-const AnimPose& AnimSkeleton::getAbsoluteBindPose(int jointIndex) const {
- return _absoluteBindPoses[jointIndex];
-}
-
-const AnimPose& AnimSkeleton::getRelativeBindPose(int jointIndex) const {
- return _relativeBindPoses[jointIndex];
-}
-
const AnimPose& AnimSkeleton::getRelativeDefaultPose(int jointIndex) const {
return _relativeDefaultPoses[jointIndex];
}
@@ -164,8 +156,6 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints)
_joints = joints;
_jointsSize = (int)joints.size();
// build a cache of bind poses
- _absoluteBindPoses.reserve(_jointsSize);
- _relativeBindPoses.reserve(_jointsSize);
// build a chache of default poses
_absoluteDefaultPoses.reserve(_jointsSize);
@@ -192,28 +182,6 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints)
} else {
_absoluteDefaultPoses.push_back(relDefaultPose);
}
-
- // build relative and absolute bind poses
- if (_joints[i].bindTransformFoundInCluster) {
- // Use the FBXJoint::bindTransform, which is absolute model coordinates
- // i.e. not relative to it's parent.
- AnimPose absoluteBindPose(_joints[i].bindTransform);
- _absoluteBindPoses.push_back(absoluteBindPose);
- if (parentIndex >= 0) {
- AnimPose inverseParentAbsoluteBindPose = _absoluteBindPoses[parentIndex].inverse();
- _relativeBindPoses.push_back(inverseParentAbsoluteBindPose * absoluteBindPose);
- } else {
- _relativeBindPoses.push_back(absoluteBindPose);
- }
- } else {
- // use default transform instead
- _relativeBindPoses.push_back(relDefaultPose);
- if (parentIndex >= 0) {
- _absoluteBindPoses.push_back(_absoluteBindPoses[parentIndex] * relDefaultPose);
- } else {
- _absoluteBindPoses.push_back(relDefaultPose);
- }
- }
}
for (int i = 0; i < _jointsSize; i++) {
@@ -251,8 +219,6 @@ void AnimSkeleton::dump(bool verbose) const {
qCDebug(animation) << " {";
qCDebug(animation) << " index =" << i;
qCDebug(animation) << " name =" << getJointName(i);
- qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
- qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i);
qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i);
if (verbose) {
@@ -287,8 +253,6 @@ void AnimSkeleton::dump(const AnimPoseVec& poses) const {
qCDebug(animation) << " {";
qCDebug(animation) << " index =" << i;
qCDebug(animation) << " name =" << getJointName(i);
- qCDebug(animation) << " absBindPose =" << getAbsoluteBindPose(i);
- qCDebug(animation) << " relBindPose =" << getRelativeBindPose(i);
qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i);
qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i);
qCDebug(animation) << " pose =" << poses[i];
diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h
index 99c9a148f7..664358f414 100644
--- a/libraries/animation/src/AnimSkeleton.h
+++ b/libraries/animation/src/AnimSkeleton.h
@@ -30,13 +30,6 @@ public:
int getNumJoints() const;
int getChainDepth(int jointIndex) const;
- // absolute pose, not relative to parent
- const AnimPose& getAbsoluteBindPose(int jointIndex) const;
-
- // relative to parent pose
- const AnimPose& getRelativeBindPose(int jointIndex) const;
- const AnimPoseVec& getRelativeBindPoses() const { return _relativeBindPoses; }
-
// the default poses are the orientations of the joints on frame 0.
const AnimPose& getRelativeDefaultPose(int jointIndex) const;
const AnimPoseVec& getRelativeDefaultPoses() const { return _relativeDefaultPoses; }
@@ -72,8 +65,6 @@ protected:
std::vector _joints;
int _jointsSize { 0 };
- AnimPoseVec _absoluteBindPoses;
- AnimPoseVec _relativeBindPoses;
AnimPoseVec _relativeDefaultPoses;
AnimPoseVec _absoluteDefaultPoses;
AnimPoseVec _relativePreRotationPoses;
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index daa1c2618f..1c9d9a3447 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -179,7 +179,7 @@ void Rig::restoreRoleAnimation(const QString& role) {
} else {
qCWarning(animation) << "Rig::restoreRoleAnimation could not find role " << role;
}
-
+
auto statesIter = _roleAnimStates.find(role);
if (statesIter != _roleAnimStates.end()) {
_roleAnimStates.erase(statesIter);
@@ -1050,52 +1050,6 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
}
}
-void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
- const QVector& freeLineage, glm::mat4 rootTransform) {
- ASSERT(false);
-}
-
-bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector& freeLineage) {
- ASSERT(false);
- return false;
-}
-
-float Rig::getLimbLength(int jointIndex, const QVector& freeLineage,
- const glm::vec3 scale, const QVector& fbxJoints) const {
- ASSERT(false);
- return 1.0f;
-}
-
-glm::quat Rig::setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority) {
- ASSERT(false);
- return glm::quat();
-}
-
-glm::vec3 Rig::getJointDefaultTranslationInConstrainedFrame(int jointIndex) {
- ASSERT(false);
- return glm::vec3();
-}
-
-glm::quat Rig::setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation, float priority, float mix) {
- ASSERT(false);
- return glm::quat();
-}
-
-bool Rig::getJointRotationInConstrainedFrame(int jointIndex, glm::quat& quatOut) const {
- ASSERT(false);
- return false;
-}
-
-void Rig::clearJointStatePriorities() {
- ASSERT(false);
-}
-
-glm::quat Rig::getJointDefaultRotationInParentFrame(int jointIndex) {
- ASSERT(false);
- return glm::quat();
-}
-
-
void Rig::updateFromEyeParameters(const EyeParameters& params) {
updateEyeJoint(params.leftEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);
updateEyeJoint(params.rightEyeJointIndex, params.modelTranslation, params.modelRotation, params.eyeLookAt, params.eyeSaccade);
diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h
index 6968d7c3af..87277af754 100644
--- a/libraries/animation/src/Rig.h
+++ b/libraries/animation/src/Rig.h
@@ -172,36 +172,6 @@ public:
// Regardless of who started the animations or how many, update the joints.
void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform);
- // legacy
- void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
- const QVector& freeLineage, glm::mat4 rootTransform);
-
- // legacy
- bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector& freeLineage);
-
- // legacy
- float getLimbLength(int jointIndex, const QVector& freeLineage,
- const glm::vec3 scale, const QVector& fbxJoints) const;
-
- // legacy
- glm::quat setJointRotationInBindFrame(int jointIndex, const glm::quat& rotation, float priority);
-
- // legacy
- glm::vec3 getJointDefaultTranslationInConstrainedFrame(int jointIndex);
-
- // legacy
- glm::quat setJointRotationInConstrainedFrame(int jointIndex, glm::quat targetRotation,
- float priority, float mix = 1.0f);
-
- // legacy
- bool getJointRotationInConstrainedFrame(int jointIndex, glm::quat& rotOut) const;
-
- // legacy
- glm::quat getJointDefaultRotationInParentFrame(int jointIndex);
-
- // legacy
- void clearJointStatePriorities();
-
void updateFromControllerParameters(const ControllerParameters& params, float dt);
void updateFromEyeParameters(const EyeParameters& params);
@@ -345,7 +315,7 @@ protected:
float firstFrame;
float lastFrame;
};
-
+
struct RoleAnimState {
RoleAnimState() {}
RoleAnimState(const QString& roleId, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
index a13ef07cd4..1112ccde60 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
@@ -237,30 +237,14 @@ bool SkeletonModel::getRightHandPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getRightHandJointIndex(), position);
}
-bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) {
- return restoreJointPosition(getLeftHandJointIndex(), fraction, priority);
-}
-
bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getLastFreeJointIndex(getLeftHandJointIndex()), position);
}
-float SkeletonModel::getLeftArmLength() const {
- return getLimbLength(getLeftHandJointIndex());
-}
-
-bool SkeletonModel::restoreRightHandPosition(float fraction, float priority) {
- return restoreJointPosition(getRightHandJointIndex(), fraction, priority);
-}
-
bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const {
return getJointPositionInWorldFrame(getLastFreeJointIndex(getRightHandJointIndex()), position);
}
-float SkeletonModel::getRightArmLength() const {
- return getLimbLength(getRightHandJointIndex());
-}
-
bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const {
return isActive() && getJointPositionInWorldFrame(getFBXGeometry().headJointIndex, headPosition);
}
diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
index f911ad0c5a..d82fce7412 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
@@ -57,11 +57,6 @@ public:
/// \return true whether or not the position was found
bool getRightHandPosition(glm::vec3& position) const;
- /// Restores some fraction of the default position of the left hand.
- /// \param fraction the fraction of the default position to restore
- /// \return whether or not the left hand joint was found
- bool restoreLeftHandPosition(float fraction = 1.0f, float priority = 1.0f);
-
/// Gets the position of the left shoulder.
/// \return whether or not the left shoulder joint was found
bool getLeftShoulderPosition(glm::vec3& position) const;
@@ -69,18 +64,10 @@ public:
/// Returns the extended length from the left hand to its last free ancestor.
float getLeftArmLength() const;
- /// Restores some fraction of the default position of the right hand.
- /// \param fraction the fraction of the default position to restore
- /// \return whether or not the right hand joint was found
- bool restoreRightHandPosition(float fraction = 1.0f, float priority = 1.0f);
-
/// Gets the position of the right shoulder.
/// \return whether or not the right shoulder joint was found
bool getRightShoulderPosition(glm::vec3& position) const;
- /// Returns the extended length from the right hand to its first free ancestor.
- float getRightArmLength() const;
-
/// Returns the position of the head joint.
/// \return whether or not the head was found
bool getHeadPosition(glm::vec3& headPosition) const;
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index d7dd2837cb..00cc760658 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -358,7 +358,7 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName NOTIFY displayNameChanged)
// sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients.
// The result is unique among all avatars present at the time.
- Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName)
+ Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged)
Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData)
@@ -685,6 +685,7 @@ public:
signals:
void displayNameChanged();
+ void sessionDisplayNameChanged();
void lookAtSnappingChanged(bool enabled);
void sessionUUIDChanged();
diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp
index 64cd534c8b..1fd001e536 100644
--- a/libraries/avatars/src/ScriptAvatarData.cpp
+++ b/libraries/avatars/src/ScriptAvatarData.cpp
@@ -15,6 +15,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) :
_avatarData(avatarData)
{
QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged);
+ QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged);
QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged);
}
diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h
index 46dfb5325f..68074b838d 100644
--- a/libraries/avatars/src/ScriptAvatarData.h
+++ b/libraries/avatars/src/ScriptAvatarData.h
@@ -44,7 +44,7 @@ class ScriptAvatarData : public QObject {
//
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
- Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName)
+ Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName NOTIFY sessionDisplayNameChanged)
Q_PROPERTY(bool isReplicated READ getIsReplicated)
Q_PROPERTY(bool lookAtSnappingEnabled READ getLookAtSnappingEnabled NOTIFY lookAtSnappingChanged)
@@ -131,6 +131,7 @@ public:
signals:
void displayNameChanged();
+ void sessionDisplayNameChanged();
void lookAtSnappingChanged(bool enabled);
public slots:
diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp
index 4337eca605..b0e2faa600 100644
--- a/libraries/fbx/src/FBXReader_Mesh.cpp
+++ b/libraries/fbx/src/FBXReader_Mesh.cpp
@@ -603,7 +603,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) {
if (!blendShape.normals.empty() && blendShape.tangents.empty()) {
// Fill with a dummy value to force tangents to be present if there are normals
blendShape.tangents.reserve(blendShape.normals.size());
- std::fill_n(std::back_inserter(fbxMesh.tangents), blendShape.normals.size(), Vectors::UNIT_X);
+ std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X);
}
}
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index b0a16bce11..72ebe2eb58 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
@@ -757,11 +758,21 @@ void AddressManager::refreshPreviousLookup() {
}
void AddressManager::copyAddress() {
+ if (QThread::currentThread() != qApp->thread()) {
+ QMetaObject::invokeMethod(this, "copyAddress");
+ return;
+ }
+
// assume that the address is being copied because the user wants a shareable address
QApplication::clipboard()->setText(currentShareableAddress().toString());
}
void AddressManager::copyPath() {
+ if (QThread::currentThread() != qApp->thread()) {
+ QMetaObject::invokeMethod(this, "copyPath");
+ return;
+ }
+
QApplication::clipboard()->setText(currentPath());
}
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 1b9ed07d6a..45c77a606d 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -1267,25 +1267,6 @@ void Model::updateClusterMatrices() {
}
}
-void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority) {
- const FBXGeometry& geometry = getFBXGeometry();
- const QVector& freeLineage = geometry.joints.at(endIndex).freeLineage;
- glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
- _rig.inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform);
-}
-
-bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) {
- const FBXGeometry& geometry = getFBXGeometry();
- const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage;
- return _rig.restoreJointPosition(jointIndex, fraction, priority, freeLineage);
-}
-
-float Model::getLimbLength(int jointIndex) const {
- const FBXGeometry& geometry = getFBXGeometry();
- const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage;
- return _rig.getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
-}
-
bool Model::maybeStartBlender() {
if (isLoaded()) {
const FBXGeometry& fbxGeometry = getFBXGeometry();
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 3252fa4257..560aa82f0c 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -199,8 +199,6 @@ public:
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;
- void inverseKinematics(int jointIndex, glm::vec3 position, const glm::quat& rotation, float priority);
-
/// Returns the extents of the model in its bind pose.
Extents getBindExtents() const;
@@ -376,16 +374,6 @@ protected:
void computeMeshPartLocalBounds();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
- /// Restores the indexed joint to its default position.
- /// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to
- /// the original position
- /// \return true if the joint was found
- bool restoreJointPosition(int jointIndex, float fraction = 1.0f, float priority = 0.0f);
-
- /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's
- /// first free ancestor.
- float getLimbLength(int jointIndex) const;
-
/// Allow sub classes to force invalidating the bboxes
void invalidCalculatedMeshBoxes() {
_triangleSetsValid = false;
diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp
index 905a224ef4..221f5013bf 100644
--- a/libraries/ui/src/OffscreenUi.cpp
+++ b/libraries/ui/src/OffscreenUi.cpp
@@ -666,7 +666,6 @@ void OffscreenUi::createDesktop(const QUrl& url) {
Q_UNUSED(context)
_desktop = static_cast(newObject);
getSurfaceContext()->setContextProperty("desktop", _desktop);
- _toolWindow = _desktop->findChild("ToolWindow");
_vrMenu = new VrMenu(this);
for (const auto& menuInitializer : _queuedMenuInitializers) {
@@ -686,10 +685,6 @@ QObject* OffscreenUi::getRootMenu() {
return getRootItem()->findChild("rootMenu");
}
-QQuickItem* OffscreenUi::getToolWindow() {
- return _toolWindow;
-}
-
void OffscreenUi::unfocusWindows() {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows");
Q_ASSERT(invokeResult);
diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h
index 76e4261cd3..ab3a209820 100644
--- a/libraries/ui/src/OffscreenUi.h
+++ b/libraries/ui/src/OffscreenUi.h
@@ -80,7 +80,6 @@ public:
QObject* getFlags();
Q_INVOKABLE bool isPointOnDesktopWindow(QVariant point);
QQuickItem* getDesktop();
- QQuickItem* getToolWindow();
QObject* getRootMenu();
enum Icon {
ICON_NONE = 0,
@@ -261,7 +260,6 @@ private:
ModalDialogListener* assetDialogAsync(const QVariantMap& properties);
QQuickItem* _desktop { nullptr };
- QQuickItem* _toolWindow { nullptr };
QList _modalDialogListeners;
std::unordered_map _pressedKeys;
VrMenu* _vrMenu { nullptr };
diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp
index 90b91c5ec2..1209d39dcf 100644
--- a/libraries/ui/src/QmlWindowClass.cpp
+++ b/libraries/ui/src/QmlWindowClass.cpp
@@ -32,7 +32,6 @@ static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge";
static const char* const WIDTH_PROPERTY = "width";
static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible";
-static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
static const uvec2 MAX_QML_WINDOW_SIZE { 1280, 720 };
static const uvec2 MIN_QML_WINDOW_SIZE { 120, 80 };
@@ -53,9 +52,6 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
if (context->argument(3).isNumber()) {
properties[HEIGHT_PROPERTY] = context->argument(3).toInt32();
}
- if (context->argument(4).isBool()) {
- properties[TOOLWINDOW_PROPERTY] = context->argument(4).toBool();
- }
} else {
properties = context->argument(0).toVariant().toMap();
}
@@ -96,52 +92,37 @@ void QmlWindowClass::initQml(QVariantMap properties) {
auto offscreenUi = DependencyManager::get();
_source = properties[SOURCE_PROPERTY].toString();
-#if QML_TOOL_WINDOW
- _toolWindow = properties.contains(TOOLWINDOW_PROPERTY) && properties[TOOLWINDOW_PROPERTY].toBool();
- if (_toolWindow) {
- // Build the event bridge and wrapper on the main thread
- _qmlWindow = offscreenUi->getToolWindow();
- properties[EVENT_BRIDGE_PROPERTY] = QVariant::fromValue(this);
- QVariant newTabVar;
- bool invokeResult = QMetaObject::invokeMethod(_qmlWindow, "addWebTab", Qt::DirectConnection,
- Q_RETURN_ARG(QVariant, newTabVar),
- Q_ARG(QVariant, QVariant::fromValue(properties)));
- Q_ASSERT(invokeResult);
- } else {
-#endif
- // Build the event bridge and wrapper on the main thread
- offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
- _qmlWindow = object;
- context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
- context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
- context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);
- if (properties.contains(TITLE_PROPERTY)) {
- object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
- }
- if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) {
- uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() };
- requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE);
- asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y));
- }
+ // Build the event bridge and wrapper on the main thread
+ offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) {
+ _qmlWindow = object;
+ context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
+ context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
+ context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership);
+ if (properties.contains(TITLE_PROPERTY)) {
+ object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString());
+ }
+ if (properties.contains(HEIGHT_PROPERTY) && properties.contains(WIDTH_PROPERTY)) {
+ uvec2 requestedSize { properties[WIDTH_PROPERTY].toUInt(), properties[HEIGHT_PROPERTY].toUInt() };
+ requestedSize = glm::clamp(requestedSize, MIN_QML_WINDOW_SIZE, MAX_QML_WINDOW_SIZE);
+ asQuickItem()->setSize(QSize(requestedSize.x, requestedSize.y));
+ }
- bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
- object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
- object->setProperty(SOURCE_PROPERTY, _source);
+ bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
+ object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
+ object->setProperty(SOURCE_PROPERTY, _source);
- const QMetaObject *metaObject = _qmlWindow->metaObject();
- // Forward messages received from QML on to the script
- connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
- connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
+ const QMetaObject *metaObject = _qmlWindow->metaObject();
+ // Forward messages received from QML on to the script
+ connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
+ connect(_qmlWindow, SIGNAL(visibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection);
+
+ if (metaObject->indexOfSignal("resized") >= 0)
+ connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);
+ if (metaObject->indexOfSignal("moved") >= 0)
+ connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
+ connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
+ });
- if (metaObject->indexOfSignal("resized") >= 0)
- connect(_qmlWindow, SIGNAL(resized(QSizeF)), this, SIGNAL(resized(QSizeF)), Qt::QueuedConnection);
- if (metaObject->indexOfSignal("moved") >= 0)
- connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection);
- connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection);
- });
-#if QML_TOOL_WINDOW
- }
-#endif
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast(_qmlWindow.data()));
}
@@ -215,11 +196,6 @@ QmlWindowClass::~QmlWindowClass() {
}
QQuickItem* QmlWindowClass::asQuickItem() const {
-#if QML_TOOL_WINDOW
- if (_toolWindow) {
- return DependencyManager::get()->getToolWindow();
- }
-#endif
return _qmlWindow.isNull() ? nullptr : dynamic_cast(_qmlWindow.data());
}
@@ -230,14 +206,6 @@ void QmlWindowClass::setVisible(bool visible) {
}
QQuickItem* targetWindow = asQuickItem();
-#if QML_TOOL_WINDOW
- if (_toolWindow) {
- // For tool window tabs we special case visibility as a function call on the tab parent
- // The tool window itself has special logic based on whether any tabs are visible
- QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible));
- return;
- }
-#endif
targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
}
@@ -253,12 +221,6 @@ bool QmlWindowClass::isVisible() {
return false;
}
-#if QML_TOOL_WINDOW
- if (_toolWindow) {
- return dynamic_cast(_qmlWindow.data())->isEnabled();
- }
-#endif
-
return asQuickItem()->isVisible();
}
@@ -343,17 +305,6 @@ void QmlWindowClass::close() {
return;
}
-#if QML_TOOL_WINDOW
- if (_toolWindow) {
- auto offscreenUi = DependencyManager::get();
- auto toolWindow = offscreenUi->getToolWindow();
- auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::DirectConnection,
- Q_ARG(QVariant, _source));
- Q_ASSERT(invokeResult);
- return;
- }
-#endif
-
if (_qmlWindow) {
_qmlWindow->deleteLater();
}
diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h
index e01bc8f14b..f274501d35 100644
--- a/libraries/ui/src/QmlWindowClass.h
+++ b/libraries/ui/src/QmlWindowClass.h
@@ -19,8 +19,6 @@
class QScriptEngine;
class QScriptContext;
-#define QML_TOOL_WINDOW 0
-
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject {
Q_OBJECT
@@ -87,12 +85,6 @@ protected:
virtual QString qmlSource() const { return "QmlWindow.qml"; }
-#if QML_TOOL_WINDOW
- // FIXME needs to be initialized in the ctor once we have support
- // for tool window panes in QML
- bool _toolWindow { false };
-#endif
-
QPointer _qmlWindow;
QString _source;
diff --git a/scripts/developer/tests/playaPerformanceTest.js b/scripts/developer/tests/playaPerformanceTest.js
index 4c8a728a15..6250fe1175 100644
--- a/scripts/developer/tests/playaPerformanceTest.js
+++ b/scripts/developer/tests/playaPerformanceTest.js
@@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({
source: qml,
height: 320,
width: 640,
- toolWindow: false,
visible: true
});
diff --git a/scripts/developer/tests/qmlTest.js b/scripts/developer/tests/qmlTest.js
index 0eaabac6d1..7d248a9cc7 100644
--- a/scripts/developer/tests/qmlTest.js
+++ b/scripts/developer/tests/qmlTest.js
@@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({
source: "qrc:///qml/OverlayWindowTest.qml",
height: 240,
width: 320,
- toolWindow: false,
visible: true
});
diff --git a/scripts/developer/tests/textureStress.js b/scripts/developer/tests/textureStress.js
index 1e3cf9a367..98af4b19b7 100644
--- a/scripts/developer/tests/textureStress.js
+++ b/scripts/developer/tests/textureStress.js
@@ -15,7 +15,6 @@ qmlWindow = new OverlayWindow({
source: qml,
height: 240,
width: 320,
- toolWindow: false,
visible: true
});
diff --git a/scripts/developer/tests/toolWindowStressTest.js b/scripts/developer/tests/toolWindowStressTest.js
deleted file mode 100644
index 44b059ebda..0000000000
--- a/scripts/developer/tests/toolWindowStressTest.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var TOGGLE_RATE = 1000;
-var RUNTIME = 60 * 1000;
-
-var webView;
-var running = true;
-
-function toggleWindow() {
- if (!running) {
- return;
- }
-
- if (webView) {
- webView.close();
- webView = null;
- } else {
- webView = new OverlayWebWindow({
- title: 'Entity Properties',
- source: "http://www.google.com",
- toolWindow: true
- });
- webView.setVisible(true);
- }
- Script.setTimeout(toggleWindow, TOGGLE_RATE)
-}
-
-toggleWindow();
-print("Creating window?")
-
-Script.setTimeout(function(){
- print("Shutting down")
-}, RUNTIME)
diff --git a/scripts/system/assets/images/run.svg b/scripts/system/assets/images/run.svg
new file mode 100644
index 0000000000..0957166346
--- /dev/null
+++ b/scripts/system/assets/images/run.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index b3e3134380..0826325a57 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -15,6 +15,7 @@
(function () { // BEGIN LOCAL_SCOPE
Script.include("/~/system/libraries/accountUtils.js");
+ var request = Script.require('request').request;
var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
@@ -51,6 +52,481 @@
tablet.sendToQml(message);
}
+ //***********************************************
+ //
+ // BEGIN Connection logic
+ //
+ //***********************************************
+ // Function Names:
+ // - requestJSON
+ // - getAvailableConnections
+ // - getInfoAboutUser
+ // - getConnectionData
+ //
+ // Description:
+ // - Update all the usernames that I am entitled to see, using my login but not dependent on canKick.
+ var METAVERSE_BASE = Account.metaverseServerURL;
+ function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
+ request({
+ uri: url
+ }, function (error, response) {
+ if (error || (response.status !== 'success')) {
+ print("Error: unable to get", url, error || response.status);
+ return;
+ }
+ callback(response.data);
+ });
+ }
+ function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise)
+ url = METAVERSE_BASE + '/api/v1/users?'
+ if (domain) {
+ url += 'status=' + domain.slice(1, -1); // without curly braces
+ } else {
+ url += 'filter=connections'; // regardless of whether online
+ }
+ requestJSON(url, function (connectionsData) {
+ callback(connectionsData.users);
+ });
+ }
+ function getInfoAboutUser(specificUsername, callback) {
+ url = METAVERSE_BASE + '/api/v1/users?filter=connections'
+ requestJSON(url, function (connectionsData) {
+ for (user in connectionsData.users) {
+ if (connectionsData.users[user].username === specificUsername) {
+ callback(connectionsData.users[user]);
+ return;
+ }
+ }
+ callback(false);
+ });
+ }
+ function getConnectionData(specificUsername, domain) {
+ function frob(user) { // get into the right format
+ var formattedSessionId = user.location.node_id || '';
+ if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) {
+ formattedSessionId = "{" + formattedSessionId + "}";
+ }
+ return {
+ sessionId: formattedSessionId,
+ userName: user.username,
+ connection: user.connection,
+ profileUrl: user.images.thumbnail,
+ placeName: (user.location.root || user.location.domain || {}).name || ''
+ };
+ }
+ if (specificUsername) {
+ getInfoAboutUser(specificUsername, function (user) {
+ if (user) {
+ updateUser(frob(user));
+ } else {
+ print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!');
+ }
+ });
+ } else {
+ getAvailableConnections(domain, function (users) {
+ if (domain) {
+ users.forEach(function (user) {
+ updateUser(frob(user));
+ });
+ } else {
+ sendToQml({ method: 'updateConnections', connections: users.map(frob) });
+ }
+ });
+ }
+ }
+ //***********************************************
+ //
+ // END Connection logic
+ //
+ //***********************************************
+
+ //***********************************************
+ //
+ // BEGIN Avatar Selector logic
+ //
+ //***********************************************
+ var UNSELECTED_TEXTURES = {
+ "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
+ "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
+ };
+ var SELECTED_TEXTURES = {
+ "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
+ "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
+ };
+ var HOVER_TEXTURES = {
+ "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"),
+ "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png")
+ };
+
+ var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 };
+ var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 };
+ var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 };
+ var conserveResources = true;
+
+ var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
+
+ function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with.
+ overlays[key] = this;
+ if (hasModel) {
+ var modelKey = key + "-m";
+ this.model = new ExtendedOverlay(modelKey, "model", {
+ url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"),
+ textures: textures(selected),
+ ignoreRayIntersection: true
+ }, false, false);
+ } else {
+ this.model = undefined;
+ }
+ this.key = key;
+ this.selected = selected || false; // not undefined
+ this.hovering = false;
+ this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+ }
+ // Instance methods:
+ ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
+ Overlays.deleteOverlay(this.activeOverlay);
+ delete overlays[this.key];
+ };
+
+ ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
+ Overlays.editOverlay(this.activeOverlay, properties);
+ };
+
+ function color(selected, hovering) {
+ var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
+ function scale(component) {
+ var delta = 0xFF - component;
+ return component;
+ }
+ return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
+ }
+
+ function textures(selected, hovering) {
+ return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES;
+ }
+ // so we don't have to traverse the overlays to get the last one
+ var lastHoveringId = 0;
+ ExtendedOverlay.prototype.hover = function (hovering) {
+ this.hovering = hovering;
+ if (this.key === lastHoveringId) {
+ if (hovering) {
+ return;
+ }
+ lastHoveringId = 0;
+ }
+ this.editOverlay({ color: color(this.selected, hovering) });
+ if (this.model) {
+ this.model.editOverlay({ textures: textures(this.selected, hovering) });
+ }
+ if (hovering) {
+ // un-hover the last hovering overlay
+ if (lastHoveringId && lastHoveringId !== this.key) {
+ ExtendedOverlay.get(lastHoveringId).hover(false);
+ }
+ lastHoveringId = this.key;
+ }
+ };
+ ExtendedOverlay.prototype.select = function (selected) {
+ if (this.selected === selected) {
+ return;
+ }
+
+ this.editOverlay({ color: color(selected, this.hovering) });
+ if (this.model) {
+ this.model.editOverlay({ textures: textures(selected) });
+ }
+ this.selected = selected;
+ };
+ // Class methods:
+ var selectedIds = [];
+ ExtendedOverlay.isSelected = function (id) {
+ return -1 !== selectedIds.indexOf(id);
+ };
+ ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
+ return overlays[key];
+ };
+ ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
+ var key;
+ for (key in overlays) {
+ if (iterator(ExtendedOverlay.get(key))) {
+ return;
+ }
+ }
+ };
+ ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
+ if (lastHoveringId) {
+ ExtendedOverlay.get(lastHoveringId).hover(false);
+ }
+ };
+
+ // hit(overlay) on the one overlay intersected by pickRay, if any.
+ // noHit() if no ExtendedOverlay was intersected (helps with hover)
+ ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
+ var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
+ if (!pickedOverlay.intersects) {
+ if (noHit) {
+ return noHit();
+ }
+ return;
+ }
+ ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
+ if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
+ hit(overlay);
+ return true;
+ }
+ });
+ };
+
+ function HighlightedEntity(id, entityProperties) {
+ this.id = id;
+ this.overlay = Overlays.addOverlay('cube', {
+ position: entityProperties.position,
+ rotation: entityProperties.rotation,
+ dimensions: entityProperties.dimensions,
+ solid: false,
+ color: {
+ red: 0xF3,
+ green: 0x91,
+ blue: 0x29
+ },
+ ignoreRayIntersection: true,
+ drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
+ });
+ HighlightedEntity.overlays.push(this);
+ }
+ HighlightedEntity.overlays = [];
+ HighlightedEntity.clearOverlays = function clearHighlightedEntities() {
+ HighlightedEntity.overlays.forEach(function (highlighted) {
+ Overlays.deleteOverlay(highlighted.overlay);
+ });
+ HighlightedEntity.overlays = [];
+ };
+ HighlightedEntity.updateOverlays = function updateHighlightedEntities() {
+ HighlightedEntity.overlays.forEach(function (highlighted) {
+ var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']);
+ Overlays.editOverlay(highlighted.overlay, {
+ position: properties.position,
+ rotation: properties.rotation,
+ dimensions: properties.dimensions
+ });
+ });
+ };
+
+
+ function addAvatarNode(id) {
+ var selected = ExtendedOverlay.isSelected(id);
+ return new ExtendedOverlay(id, "sphere", {
+ drawInFront: true,
+ solid: true,
+ alpha: 0.8,
+ color: color(selected, false),
+ ignoreRayIntersection: false
+ }, selected, !conserveResources);
+ }
+
+ var pingPong = true;
+ function updateOverlays() {
+ var eye = Camera.position;
+ AvatarList.getAvatarIdentifiers().forEach(function (id) {
+ if (!id) {
+ return; // don't update ourself, or avatars we're not interested in
+ }
+ var avatar = AvatarList.getAvatar(id);
+ if (!avatar) {
+ return; // will be deleted below if there had been an overlay.
+ }
+ var overlay = ExtendedOverlay.get(id);
+ if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
+ overlay = addAvatarNode(id);
+ }
+ var target = avatar.position;
+ var distance = Vec3.distance(target, eye);
+ var offset = 0.2;
+ var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
+ var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
+ if (headIndex > 0) {
+ offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
+ }
+
+ // move a bit in front, towards the camera
+ target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
+
+ // now bump it up a bit
+ target.y = target.y + offset;
+
+ overlay.ping = pingPong;
+ overlay.editOverlay({
+ color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
+ position: target,
+ dimensions: 0.032 * distance
+ });
+ if (overlay.model) {
+ overlay.model.ping = pingPong;
+ overlay.model.editOverlay({
+ position: target,
+ scale: 0.2 * distance, // constant apparent size
+ rotation: Camera.orientation
+ });
+ }
+ });
+ pingPong = !pingPong;
+ ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
+ if (overlay.ping === pingPong) {
+ overlay.deleteOverlay();
+ }
+ });
+ // We could re-populateNearbyUserList if anything added or removed, but not for now.
+ HighlightedEntity.updateOverlays();
+ }
+ function removeOverlays() {
+ selectedIds = [];
+ lastHoveringId = 0;
+ HighlightedEntity.clearOverlays();
+ ExtendedOverlay.some(function (overlay) {
+ overlay.deleteOverlay();
+ });
+ }
+
+ //
+ // Clicks.
+ //
+ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
+ if (selectedIds[0] === id) {
+ var message = {
+ method: 'updateSelectedRecipientUsername',
+ userName: username === "" ? "unknown username" : username
+ };
+ sendToQml(message);
+ }
+ }
+ function handleClick(pickRay) {
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ var nextSelectedStatus = !overlay.selected;
+ var avatarId = overlay.key;
+ selectedIds = nextSelectedStatus ? [avatarId] : [];
+ if (nextSelectedStatus) {
+ Users.requestUsernameFromID(avatarId);
+ }
+ var message = {
+ method: 'selectRecipient',
+ id: [avatarId],
+ isSelected: nextSelectedStatus,
+ displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
+ userName: ''
+ };
+ sendToQml(message);
+
+ ExtendedOverlay.some(function (overlay) {
+ var id = overlay.key;
+ var selected = ExtendedOverlay.isSelected(id);
+ overlay.select(selected);
+ });
+
+ HighlightedEntity.clearOverlays();
+ if (selectedIds.length) {
+ Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) {
+ // Because lastEditedBy is per session, the vast majority of entities won't match,
+ // so it would probably be worth reducing marshalling costs by asking for just we need.
+ // However, providing property name(s) is advisory and some additional properties are
+ // included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation',
+ // and 'dimensions', too, so we might as well make use of them instead of making a second
+ // getEntityProperties call.
+ // It would be nice if we could harden this against future changes by specifying all
+ // and only these four in an array, but see
+ // https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work
+ var properties = Entities.getEntityProperties(id, 'lastEditedBy');
+ if (ExtendedOverlay.isSelected(properties.lastEditedBy)) {
+ new HighlightedEntity(id, properties);
+ }
+ });
+ }
+ return true;
+ });
+ }
+ function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
+ if (!mousePressEvent.isLeftButton) {
+ return;
+ }
+ handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+ }
+ function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ overlay.hover(true);
+ }, function () {
+ ExtendedOverlay.unHover();
+ });
+ }
+
+ // handy global to keep track of which hand is the mouse (if any)
+ var currentHandPressed = 0;
+ var TRIGGER_CLICK_THRESHOLD = 0.85;
+ var TRIGGER_PRESS_THRESHOLD = 0.05;
+
+ function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
+ var pickRay;
+ if (HMD.active) {
+ if (currentHandPressed !== 0) {
+ pickRay = controllerComputePickRay(currentHandPressed);
+ } else {
+ // nothing should hover, so
+ ExtendedOverlay.unHover();
+ return;
+ }
+ } else {
+ pickRay = Camera.computePickRay(event.x, event.y);
+ }
+ handleMouseMove(pickRay);
+ }
+ function handleTriggerPressed(hand, value) {
+ // The idea is if you press one trigger, it is the one
+ // we will consider the mouse. Even if the other is pressed,
+ // we ignore it until this one is no longer pressed.
+ var isPressed = value > TRIGGER_PRESS_THRESHOLD;
+ if (currentHandPressed === 0) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
+ }
+ if (currentHandPressed === hand) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
+ }
+ // otherwise, the other hand is still triggered
+ // so do nothing.
+ }
+
+ // We get mouseMoveEvents from the handControllers, via handControllerPointer.
+ // But we don't get mousePressEvents.
+ var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
+ var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
+ function controllerComputePickRay(hand) {
+ var controllerPose = getControllerWorldLocation(hand, true);
+ if (controllerPose.valid) {
+ return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
+ }
+ }
+ function makeClickHandler(hand) {
+ return function (clicked) {
+ if (clicked > TRIGGER_CLICK_THRESHOLD) {
+ var pickRay = controllerComputePickRay(hand);
+ handleClick(pickRay);
+ }
+ };
+ }
+ function makePressHandler(hand) {
+ return function (value) {
+ handleTriggerPressed(hand, value);
+ };
+ }
+ triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
+ triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
+ triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
+ triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
+ //***********************************************
+ //
+ // END Avatar Selector logic
+ //
+ //***********************************************
+
// Function Name: fromQml()
//
// Description:
@@ -110,6 +586,17 @@
case 'goToMarketplaceItemPage':
tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
break;
+ case 'refreshConnections':
+ print('Refreshing Connections...');
+ getConnectionData(false);
+ break;
+ case 'enable_ChooseRecipientNearbyMode':
+ Script.update.connect(updateOverlays);
+ break;
+ case 'disable_ChooseRecipientNearbyMode':
+ Script.update.disconnect(updateOverlays);
+ removeOverlays();
+ break;
default:
print('Unrecognized message from QML:', JSON.stringify(message));
}
@@ -154,6 +641,17 @@
if (button) {
button.editProperties({ isActive: onWalletScreen });
}
+
+ if (onWalletScreen) {
+ isWired = true;
+ Users.usernameFromIDReply.connect(usernameFromIDReply);
+ Controller.mousePressEvent.connect(handleMouseEvent);
+ Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+ triggerMapping.enable();
+ triggerPressMapping.enable();
+ } else {
+ off();
+ }
}
//
@@ -176,6 +674,19 @@
tablet.screenChanged.connect(onTabletScreenChanged);
}
}
+ var isWired = false;
+ function off() {
+ if (isWired) { // It is not ok to disconnect these twice, hence guard.
+ Users.usernameFromIDReply.disconnect(usernameFromIDReply);
+ Script.update.disconnect(updateOverlays);
+ Controller.mousePressEvent.disconnect(handleMouseEvent);
+ Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
+ isWired = false;
+ }
+ triggerMapping.disable(); // It's ok if we disable twice.
+ triggerPressMapping.disable(); // see above
+ removeOverlays();
+ }
function shutdown() {
button.clicked.disconnect(onButtonClicked);
tablet.removeButton(button);
@@ -185,6 +696,7 @@
tablet.gotoHomeScreen();
}
}
+ off();
}
//
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 8819960354..ee3a9ce7ec 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -575,6 +575,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
method: 'purchases_showMyItems'
});
break;
+ case 'refreshConnections':
+ case 'enable_ChooseRecipientNearbyMode':
+ case 'disable_ChooseRecipientNearbyMode':
+ // NOP
+ break;
default:
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
}
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index ed7059f9f3..1b93bdde32 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -685,7 +685,6 @@ function startup() {
});
button.clicked.connect(onTabletButtonClicked);
tablet.screenChanged.connect(onTabletScreenChanged);
- Users.usernameFromIDReply.connect(usernameFromIDReply);
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
Messages.subscribe(CHANNEL);
@@ -708,6 +707,7 @@ function off() {
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
+ Users.usernameFromIDReply.disconnect(usernameFromIDReply);
isWired = false;
ContextOverlay.enabled = true
}
@@ -744,6 +744,7 @@ function onTabletButtonClicked() {
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+ Users.usernameFromIDReply.connect(usernameFromIDReply);
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
@@ -890,7 +891,6 @@ function shutdown() {
button.clicked.disconnect(onTabletButtonClicked);
tablet.removeButton(button);
tablet.screenChanged.disconnect(onTabletScreenChanged);
- Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
Messages.subscribe(CHANNEL);
diff --git a/scripts/system/run.js b/scripts/system/run.js
new file mode 100644
index 0000000000..054cca6d9c
--- /dev/null
+++ b/scripts/system/run.js
@@ -0,0 +1,39 @@
+"use strict";
+
+/* global Script, Tablet, MyAvatar */
+
+(function() { // BEGIN LOCAL_SCOPE
+ var WALK_SPEED = 2.6;
+ var RUN_SPEED = 4.5;
+ var MIDDLE_SPEED = (WALK_SPEED + RUN_SPEED) / 2.0;
+
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ var button = tablet.addButton({
+ icon: Script.resolvePath("assets/images/run.svg"),
+ text: "RUN",
+ sortOrder: 15
+ });
+
+ function onClicked() {
+ if (MyAvatar.walkSpeed < MIDDLE_SPEED) {
+ button.editProperties({isActive: true});
+ MyAvatar.walkSpeed = RUN_SPEED;
+ } else {
+ button.editProperties({isActive: false});
+ MyAvatar.walkSpeed = WALK_SPEED;
+ }
+ }
+
+ function cleanup() {
+ button.clicked.disconnect(onClicked);
+ tablet.removeButton(button);
+ }
+
+ button.clicked.connect(onClicked);
+ if (MyAvatar.walkSpeed < MIDDLE_SPEED) {
+ button.editProperties({isActive: false});
+ } else {
+ button.editProperties({isActive: true});
+ }
+
+}());