mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 16:43:33 +02:00
Merge remote-tracking branch 'upstream/master' into android
This commit is contained in:
commit
cbb2d9d854
48 changed files with 2593 additions and 726 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
//
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
//
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
1502
interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml
Normal file
1502
interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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" },
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
|
||||
|
||||
// max unscaled forward movement speed
|
||||
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
|
@ -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<AccountManager>();
|
||||
|
@ -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("<font color='#%1'> %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("<font color='#B70A37'>-%1 HFC</font>").arg(sent);
|
||||
if (received > 0) {
|
||||
result += QString("<br>");
|
||||
}
|
||||
result += QString((certs == 1) ? " %1 certificate" : " %1 certificates").arg(certs);
|
||||
}
|
||||
return result + QString("</font>");
|
||||
if (received > 0) {
|
||||
result += QString("<font color='#3AA38F'>%1 HFC</font>").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("<a href=\"%1%2\">%2</a>").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("<br>with memo: <i>\"%1\"</i>").arg(message);
|
||||
}
|
||||
} else {
|
||||
result += valueObject["message"].toString();
|
||||
}
|
||||
// no matter what we append a smaller date to the bottom of this...
|
||||
|
||||
result += QString("<br><font size='-2' color='#1080B8'>%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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
|
||||
|
@ -137,3 +139,27 @@ void QmlCommerce::certificateInfo(const QString& certificateId) {
|
|||
auto ledger = DependencyManager::get<Ledger>();
|
||||
ledger->certificateInfo(certificateId);
|
||||
}
|
||||
|
||||
void QmlCommerce::transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
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<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1255,7 +1255,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
// / /
|
||||
// O--O O--O
|
||||
|
||||
loadDefaultPoses(_skeleton->getRelativeBindPoses());
|
||||
loadDefaultPoses(_skeleton->getRelativeDefaultPoses());
|
||||
|
||||
int numJoints = (int)_defaultRelativePoses.size();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<FBXJoint>& 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<FBXJoint>& 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];
|
||||
|
|
|
@ -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<FBXJoint> _joints;
|
||||
int _jointsSize { 0 };
|
||||
AnimPoseVec _absoluteBindPoses;
|
||||
AnimPoseVec _relativeBindPoses;
|
||||
AnimPoseVec _relativeDefaultPoses;
|
||||
AnimPoseVec _absoluteDefaultPoses;
|
||||
AnimPoseVec _relativePreRotationPoses;
|
||||
|
|
|
@ -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<int>& freeLineage, glm::mat4 rootTransform) {
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage) {
|
||||
ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
float Rig::getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
||||
const glm::vec3 scale, const QVector<FBXJoint>& 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);
|
||||
|
|
|
@ -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<int>& freeLineage, glm::mat4 rootTransform);
|
||||
|
||||
// legacy
|
||||
bool restoreJointPosition(int jointIndex, float fraction, float priority, const QVector<int>& freeLineage);
|
||||
|
||||
// legacy
|
||||
float getLimbLength(int jointIndex, const QVector<int>& freeLineage,
|
||||
const glm::vec3 scale, const QVector<FBXJoint>& 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) :
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> attachmentData READ getAttachmentData WRITE setAttachmentData)
|
||||
|
@ -685,6 +685,7 @@ public:
|
|||
|
||||
signals:
|
||||
void displayNameChanged();
|
||||
void sessionDisplayNameChanged();
|
||||
void lookAtSnappingChanged(bool enabled);
|
||||
void sessionUUIDChanged();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#include <QThread>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
#include <GLMHelpers.h>
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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<int>& 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<int>& 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<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||
return _rig.getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
|
||||
}
|
||||
|
||||
bool Model::maybeStartBlender() {
|
||||
if (isLoaded()) {
|
||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -666,7 +666,6 @@ void OffscreenUi::createDesktop(const QUrl& url) {
|
|||
Q_UNUSED(context)
|
||||
_desktop = static_cast<QQuickItem*>(newObject);
|
||||
getSurfaceContext()->setContextProperty("desktop", _desktop);
|
||||
_toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
|
||||
|
||||
_vrMenu = new VrMenu(this);
|
||||
for (const auto& menuInitializer : _queuedMenuInitializers) {
|
||||
|
@ -686,10 +685,6 @@ QObject* OffscreenUi::getRootMenu() {
|
|||
return getRootItem()->findChild<QObject*>("rootMenu");
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenUi::getToolWindow() {
|
||||
return _toolWindow;
|
||||
}
|
||||
|
||||
void OffscreenUi::unfocusWindows() {
|
||||
bool invokeResult = QMetaObject::invokeMethod(_desktop, "unfocusWindows");
|
||||
Q_ASSERT(invokeResult);
|
||||
|
|
|
@ -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<QObject*> _modalDialogListeners;
|
||||
std::unordered_map<int, bool> _pressedKeys;
|
||||
VrMenu* _vrMenu { nullptr };
|
||||
|
|
|
@ -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<OffscreenUi>();
|
||||
_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<const QQuickItem*>(_qmlWindow.data()));
|
||||
}
|
||||
|
@ -215,11 +196,6 @@ QmlWindowClass::~QmlWindowClass() {
|
|||
}
|
||||
|
||||
QQuickItem* QmlWindowClass::asQuickItem() const {
|
||||
#if QML_TOOL_WINDOW
|
||||
if (_toolWindow) {
|
||||
return DependencyManager::get<OffscreenUi>()->getToolWindow();
|
||||
}
|
||||
#endif
|
||||
return _qmlWindow.isNull() ? nullptr : dynamic_cast<QQuickItem*>(_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<QQuickItem*>(_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<OffscreenUi>();
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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<QObject> _qmlWindow;
|
||||
QString _source;
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({
|
|||
source: qml,
|
||||
height: 320,
|
||||
width: 640,
|
||||
toolWindow: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ qmlWindow = new OverlayWindow({
|
|||
source: "qrc:///qml/OverlayWindowTest.qml",
|
||||
height: 240,
|
||||
width: 320,
|
||||
toolWindow: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ qmlWindow = new OverlayWindow({
|
|||
source: qml,
|
||||
height: 240,
|
||||
width: 320,
|
||||
toolWindow: false,
|
||||
visible: true
|
||||
});
|
||||
|
||||
|
|
|
@ -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)
|
1
scripts/system/assets/images/run.svg
Normal file
1
scripts/system/assets/images/run.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M56.3,18.2c-2.6,0-28.1,0-32.5,0s-5.3,7.1,0,7.1h23.7c0.9,0,1.2,0.6,0.8,1.3c0,0-6,10.8-8.3,14.3c-2.3,3.5-1.1,9,1.7,11 C49.1,57.4,60.5,66,60.5,66c0.7,0.5,1.2,1.6,1.2,2.4c0,0,0,15.2,0,21.7c0,6.5,9.6,6.2,9.6,0c0-4.9,0-27.1,0-27.1 c0-0.8-0.5-1.9-1.2-2.5L53.8,48.5C53.2,48,53,47,53.4,46.3l10.2-17.9C63.5,28.4,58.9,18.2,56.3,18.2z M38.1,53.8l-8.6,12.1 c0,0-4.4-3.1-9.5-7.2c-5-4.1-10.9,2.8-5.8,6.8c2.6,2,6.8,5.2,10.3,8c3.6,2.8,9,3.2,12.6-2.1c5.2-7.8,8.2-12,8.2-12L38.1,53.8z M61.2,39.8c0,0,4.8,7,6.8,10.1c2.1,3,6.7,2.3,9.8,0c3.1-2.3,6.1-4.3,8.3-6c3.6-2.7-0.1-8.6-3.9-6c-4.7,3.3-7.7,5.7-7.7,5.7 c-0.6,0.5-1.5,0.4-2-0.4L65.9,32L61.2,39.8z M61.8,7.5c-3.7,6.2,0.9,12.7,3.8,14.4c2.1,1.3,5.1,2.6,8.3-2.7s3.3-8.8-3-12.6 C68.8,5.4,64.3,3.3,61.8,7.5z"></path></svg>
|
After Width: | Height: | Size: 955 B |
|
@ -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();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
39
scripts/system/run.js
Normal file
39
scripts/system/run.js
Normal file
|
@ -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});
|
||||
}
|
||||
|
||||
}());
|
Loading…
Reference in a new issue