diff --git a/interface/resources/icons/tablet-icons/EmoteAppIcon.svg b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg new file mode 100644 index 0000000000..340f0fcd2f --- /dev/null +++ b/interface/resources/icons/tablet-icons/EmoteAppIcon.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/interface/resources/images/preview-privacy.png b/interface/resources/images/preview-privacy.png new file mode 100644 index 0000000000..1a718de233 Binary files /dev/null and b/interface/resources/images/preview-privacy.png differ diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index 814c07cb8a..b061dc0a8e 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -23,11 +23,14 @@ TextField { property bool isSearchField: false property string label: "" property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0) + property bool hasDefocusedBorder: true; property bool hasRoundedBorder: false + property int roundedBorderRadius: 4 property bool error: false; property bool hasClearButton: false; property alias textColor: textField.color - property string leftPlaceholderGlyph: ""; + property string leftPermanentGlyph: ""; + property string centerPlaceholderGlyph: ""; FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; } @@ -113,12 +116,12 @@ TextField { } } border.color: textField.error ? hifi.colors.redHighlight : - (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + (textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color)) border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0 - radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0) + radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0) HiFiGlyphs { - text: textField.leftPlaceholderGlyph; + text: textField.leftPermanentGlyph; color: textColor; size: hifi.fontSizes.textFieldSearchIcon; anchors.left: parent.left; @@ -128,6 +131,15 @@ TextField { } HiFiGlyphs { + text: textField.centerPlaceholderGlyph; + color: textColor; + size: parent.height; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + visible: text && !textField.focus && textField.text === ""; + } + + HiFiGlyphs { text: hifi.glyphs.search color: textColor size: hifi.fontSizes.textFieldSearchIcon @@ -158,7 +170,7 @@ TextField { property color placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - leftPadding: (isSearchField ? textField.height - 2 : 0) + hifi.dimensions.textPadding + leftPadding: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding rightPadding: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 3c60e1beda..f0f83c1885 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -388,7 +388,7 @@ Rectangle { // Item { id: tabButtonsContainer; - visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange"; + visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendMoneyStep"; property int numTabs: 5; // Size width: root.width; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml index c2d9ef3b19..33cd43bb05 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/ConnectionItem.qml @@ -29,13 +29,13 @@ Item { property string userName; property string profilePicUrl; - height: 65; + height: 75; width: parent.width; Rectangle { id: mainContainer; // Style - color: root.isSelected ? hifi.colors.faintGray : hifi.colors.white; + color: root.isSelected ? hifi.colors.faintGray80 : hifi.colors.white; // Size anchors.left: parent.left; anchors.right: parent.right; @@ -49,7 +49,7 @@ Item { anchors.verticalCenter: parent.verticalCenter; anchors.left: parent.left; anchors.leftMargin: 36; - height: root.height - 15; + height: 50; width: visible ? height : 0; clip: true; Image { @@ -83,15 +83,15 @@ Item { RalewaySemiBold { id: userName; anchors.left: avatarImage.right; - anchors.leftMargin: 16; + anchors.leftMargin: 12; 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; + size: 18; // Style - color: hifi.colors.baseGray; + color: hifi.colors.blueAccent; text: root.userName; elide: Text.ElideRight; // Alignment @@ -107,9 +107,9 @@ Item { colorScheme: hifi.colorSchemes.dark; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; - anchors.rightMargin: 24; - height: root.height - 20; - width: 110; + anchors.rightMargin: 28; + height: 35; + width: 100; text: "CHOOSE"; onClicked: { var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl }; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml index 267cf0ed51..43636d47ca 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/RecipientDisplay.qml @@ -29,6 +29,7 @@ Item { property string displayName; property string userName; property string profilePic; + property string textColor: hifi.colors.white; Item { visible: root.isDisplayingNearby; @@ -46,7 +47,7 @@ Item { // Text size size: 18; // Style - color: hifi.colors.baseGray; + color: root.textColor; verticalAlignment: Text.AlignBottom; elide: Text.ElideRight; } @@ -63,7 +64,7 @@ Item { // Text size size: 16; // Style - color: hifi.colors.baseGray; + color: root.textColor; verticalAlignment: Text.AlignTop; elide: Text.ElideRight; } @@ -108,7 +109,7 @@ Item { // Text size size: 16; // Style - color: hifi.colors.baseGray; + color: root.textColor; verticalAlignment: Text.AlignVCenter; elide: Text.ElideRight; } diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index 9164ba7a8c..f66781c919 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -29,8 +29,9 @@ Item { property int parentAppNavBarHeight; property string currentActiveView: "sendMoneyHome"; property string nextActiveView: ""; - property bool isCurrentlyFullScreen: chooseRecipientConnection.visible || - chooseRecipientNearby.visible || sendMoneyStep.visible || paymentSuccess.visible || paymentFailure.visible; + property bool shouldShowTopAndBottomOfWallet: chooseRecipientConnection.visible || + chooseRecipientNearby.visible || paymentSuccess.visible || paymentFailure.visible; + property bool shouldShowTopOfWallet: sendMoneyStep.visible; property bool isCurrentlySendingMoney: false; // This object is always used in a popup or full-screen Wallet section. @@ -38,9 +39,9 @@ Item { // able to click on a button/mouseArea underneath the popup/section. MouseArea { x: 0; - y: root.isCurrentlyFullScreen ? 0 : root.parentAppTitleBarHeight; + y: (root.shouldShowTopAndBottomOfWallet && !root.shouldShowTopOfWallet) ? 0 : root.parentAppTitleBarHeight; width: parent.width; - height: root.isCurrentlyFullScreen ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight; + height: (root.shouldShowTopAndBottomOfWallet || root.shouldShowTopOfWallet) ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight; propagateComposedEvents: false; } @@ -105,7 +106,8 @@ Item { // Send Money Home BEGIN Item { id: sendMoneyHome; - visible: root.currentActiveView === "sendMoneyHome"; + z: 996; + visible: root.currentActiveView === "sendMoneyHome" || root.currentActiveView === "chooseRecipientConnection" || root.currentActiveView === "chooseRecipientNearby"; anchors.fill: parent; anchors.topMargin: root.parentAppTitleBarHeight; anchors.bottomMargin: root.parentAppNavBarHeight; @@ -194,6 +196,17 @@ Item { anchors.right: parent.right; anchors.bottom: parent.bottom; height: 440; + + + LinearGradient { + anchors.fill: parent; + start: Qt.point(0, 0); + end: Qt.point(0, height); + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.white } + GradientStop { position: 1.0; color: hifi.colors.faintGray } + } + } RalewaySemiBold { id: sendMoneyText; @@ -223,12 +236,13 @@ Item { Image { anchors.top: parent.top; - source: "../images/wallet-bg.jpg"; - height: 60; + source: "./images/connection.svg"; + height: 70; width: parent.width; fillMode: Image.PreserveAspectFit; horizontalAlignment: Image.AlignHCenter; verticalAlignment: Image.AlignTop; + mipmap: true; } RalewaySemiBold { @@ -248,6 +262,7 @@ Item { anchors.fill: parent; onClicked: { root.nextActiveView = "chooseRecipientConnection"; + filterBar.text = ""; } } } @@ -264,12 +279,13 @@ Item { Image { anchors.top: parent.top; - source: "../images/wallet-bg.jpg"; - height: 60; + source: "./images/nearby.svg"; + height: 70; width: parent.width; fillMode: Image.PreserveAspectFit; horizontalAlignment: Image.AlignHCenter; verticalAlignment: Image.AlignTop; + mipmap: true; } RalewaySemiBold { @@ -299,9 +315,18 @@ Item { // Choose Recipient Connection BEGIN Rectangle { id: chooseRecipientConnection; + z: 997; visible: root.currentActiveView === "chooseRecipientConnection"; anchors.fill: parent; - color: "#AAAAAA"; + color: "#80000000"; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } ListModel { id: connectionsModel; @@ -314,6 +339,8 @@ Item { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; + radius: 8; RalewaySemiBold { id: chooseRecipientText_connections; @@ -322,11 +349,11 @@ Item { anchors.top: parent.top; anchors.topMargin: 26; anchors.left: parent.left; - anchors.leftMargin: 20; + anchors.leftMargin: 36; width: paintedWidth; height: 30; // Text size - size: 22; + size: 18; // Style color: hifi.colors.baseGray; } @@ -334,6 +361,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_connections; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -363,7 +391,7 @@ Item { height: 40; // Anchors anchors.left: parent.left; - anchors.leftMargin: 16; + anchors.leftMargin: 36; anchors.right: parent.right; anchors.rightMargin: 16; anchors.top: chooseRecipientText_connections.bottom; @@ -374,8 +402,10 @@ Item { colorScheme: hifi.colorSchemes.faintGray; hasClearButton: true; hasRoundedBorder: true; + hasDefocusedBorder: false; + roundedBorderRadius: filterBar.height/2; anchors.fill: parent; - placeholderText: "filter recipients"; + centerPlaceholderGlyph: hifi.glyphs.search; onTextChanged: { buildFilteredConnectionsModel(); @@ -461,17 +491,28 @@ Item { // Choose Recipient Nearby BEGIN Rectangle { id: chooseRecipientNearby; + z: 997; + color: "#80000000"; property string selectedRecipient; visible: root.currentActiveView === "chooseRecipientNearby"; anchors.fill: parent; - color: "#AAAAAA"; + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; + radius: 8; RalewaySemiBold { text: "Choose Recipient:"; @@ -483,7 +524,7 @@ Item { width: paintedWidth; height: 30; // Text size - size: 22; + size: 18; // Style color: hifi.colors.baseGray; } @@ -491,6 +532,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_nearby; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -512,163 +554,142 @@ Item { } } - Item { - id: selectionInstructionsContainer; - visible: chooseRecipientNearby.selectedRecipient === ""; - anchors.fill: parent; - - RalewaySemiBold { - id: selectionInstructions_deselected; - text: "Click/trigger on an avatar nearby to select them..."; - // Anchors - anchors.bottom: parent.bottom; - anchors.bottomMargin: 200; - anchors.left: parent.left; - anchors.leftMargin: 58; - anchors.right: parent.right; - anchors.rightMargin: anchors.leftMargin; - height: paintedHeight; - // Text size - size: 20; - // Style - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - wrapMode: Text.Wrap; - } + RalewaySemiBold { + id: selectionInstructions; + text: chooseRecipientNearby.selectedRecipient === "" ? "Trigger or click on\nsomeone nearby to select them" : + "Trigger or click on\nsomeone else to select again"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 100; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: anchors.leftMargin; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + wrapMode: Text.Wrap; } - Item { + Image { + anchors.top: selectionInstructions.bottom; + anchors.topMargin: 20; + anchors.bottom: selectionMadeContainer.top; + anchors.bottomMargin: 30; + source: "./images/p2p-nearby-unselected.svg"; + width: parent.width; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignVCenter; + mipmap: true; + } + + Rectangle { id: selectionMadeContainer; - visible: !selectionInstructionsContainer.visible; - anchors.fill: parent; + visible: chooseRecipientNearby.selectedRecipient !== ""; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 190; + color: "#F3F3F3"; + radius: 8; + + // Used to square off the top left and top right edges of this container + Rectangle { + color: "#F3F3F3"; + height: selectionMadeContainer.radius; + anchors.top: selectionMadeContainer.top; + anchors.left: selectionMadeContainer.left; + anchors.right: selectionMadeContainer.right; + } RalewaySemiBold { id: sendToText; - text: "Send To:"; + text: "Send to:"; // Anchors anchors.top: parent.top; - anchors.topMargin: 120; + anchors.topMargin: 36; anchors.left: parent.left; - anchors.leftMargin: 12; + anchors.leftMargin: 36; width: paintedWidth; height: paintedHeight; // Text size - size: 20; + size: 18; // Style color: hifi.colors.baseGray; } + Image { + id: selectedImage; + anchors.top: parent.top; + anchors.topMargin: 24; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 32; + anchors.left: sendToText.right; + anchors.leftMargin: 4; + source: "./images/p2p-nearby-selected.svg"; + width: 50; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignVCenter; + mipmap: true; + } + RalewaySemiBold { id: avatarDisplayName; text: '"' + AvatarList.getAvatar(chooseRecipientNearby.selectedRecipient).sessionDisplayName + '"'; // Anchors - anchors.top: sendToText.bottom; - anchors.topMargin: 60; - anchors.left: parent.left; - anchors.leftMargin: 30; + anchors.top: parent.top; + anchors.topMargin: 34; + anchors.left: selectedImage.right; + anchors.leftMargin: 10; anchors.right: parent.right; - anchors.rightMargin: 30; + anchors.rightMargin: 10; height: paintedHeight; // Text size - size: 22; + size: 20; // Style - horizontalAlignment: Text.AlignHCenter; - color: hifi.colors.baseGray; - } - - RalewaySemiBold { - id: avatarNodeID; - text: chooseRecipientNearby.selectedRecipient; - // Anchors - anchors.top: avatarDisplayName.bottom; - anchors.topMargin: 6; - anchors.left: parent.left; - anchors.leftMargin: 30; - anchors.right: parent.right; - anchors.rightMargin: 30; - height: paintedHeight; - // Text size - size: 14; - // Style - horizontalAlignment: Text.AlignHCenter; - color: hifi.colors.lightGrayText; + color: hifi.colors.blueAccent; } RalewaySemiBold { id: avatarUserName; text: sendMoneyStep.selectedRecipientUserName; // Anchors - anchors.top: avatarNodeID.bottom; - anchors.topMargin: 12; - anchors.left: parent.left; - anchors.leftMargin: 30; + anchors.top: avatarDisplayName.bottom; + anchors.topMargin: 16; + anchors.left: selectedImage.right; + anchors.leftMargin: 10; anchors.right: parent.right; - anchors.rightMargin: 30; + anchors.rightMargin: 10; height: paintedHeight; // Text size - size: 22; + size: 18; // Style - horizontalAlignment: Text.AlignHCenter; color: hifi.colors.baseGray; } - RalewaySemiBold { - id: selectionInstructions_selected; - text: "Click/trigger on another avatar nearby to select them...\n\nor press 'Next' to continue."; - // Anchors + // "CHOOSE" button + HifiControlsUit.Button { + id: chooseButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; - anchors.bottomMargin: 200; - anchors.left: parent.left; - anchors.leftMargin: 58; - anchors.right: parent.right; - anchors.rightMargin: anchors.leftMargin; - height: paintedHeight; - // Text size - size: 20; - // Style - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - wrapMode: Text.Wrap; - } - } + anchors.bottomMargin: 20; + height: 40; + width: 110; + text: "CHOOSE"; + onClicked: { + sendMoneyStep.referrer = "nearby"; + sendMoneyStep.selectedRecipientNodeID = chooseRecipientNearby.selectedRecipient; + chooseRecipientNearby.selectedRecipient = ""; - // "Cancel" button - HifiControlsUit.Button { - id: cancelButton; - color: hifi.buttons.noneBorderless; - colorScheme: hifi.colorSchemes.dark; - anchors.left: parent.left; - anchors.leftMargin: 60; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; - height: 50; - width: 120; - text: "Cancel"; - onClicked: { - root.nextActiveView = "sendMoneyHome"; - resetSendMoneyData(); - } - } - - // "Next" button - HifiControlsUit.Button { - id: nextButton; - enabled: chooseRecipientNearby.selectedRecipient !== ""; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.rightMargin: 60; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; - height: 50; - width: 120; - text: "Next"; - onClicked: { - sendMoneyStep.referrer = "nearby"; - sendMoneyStep.selectedRecipientNodeID = chooseRecipientNearby.selectedRecipient; - chooseRecipientNearby.selectedRecipient = ""; - - root.nextActiveView = "sendMoneyStep"; + root.nextActiveView = "sendMoneyStep"; + } } } } @@ -676,9 +697,9 @@ Item { // Choose Recipient Nearby END // Send Money Screen BEGIN - Rectangle { + Item { id: sendMoneyStep; - z: 997; + z: 996; property string referrer; // either "connections" or "nearby" property string selectedRecipientNodeID; @@ -688,284 +709,333 @@ Item { visible: root.currentActiveView === "sendMoneyStep"; anchors.fill: parent; - color: "#AAAAAA"; + anchors.topMargin: root.parentAppTitleBarHeight; - Rectangle { - anchors.centerIn: parent; - width: parent.width - 30; - height: parent.height - 30; + RalewaySemiBold { + id: sendMoneyText_sendMoneyStep; + text: "Send Money"; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 26; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: paintedWidth; + height: 30; + // Text size + size: 22; + // Style + color: hifi.colors.white; + } + + Item { + id: sendToContainer; + anchors.top: sendMoneyText_sendMoneyStep.bottom; + anchors.topMargin: 20; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; RalewaySemiBold { - id: sendMoneyText_sendMoneyStep; - text: "Send Money To:"; + id: sendToText_sendMoneyStep; + text: "Send to:"; // Anchors anchors.top: parent.top; - anchors.topMargin: 26; anchors.left: parent.left; - anchors.leftMargin: 20; - width: paintedWidth; - height: 30; + anchors.bottom: parent.bottom; + width: 90; // Text size - size: 22; + size: 18; // Style - color: hifi.colors.baseGray; + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; } - Item { - id: sendToContainer; - anchors.top: sendMoneyText_sendMoneyStep.bottom; - anchors.topMargin: 20; - anchors.left: parent.left; - anchors.leftMargin: 20; + RecipientDisplay { + anchors.top: parent.top; + anchors.left: sendToText_sendMoneyStep.right; + anchors.right: changeButton.left; + anchors.rightMargin: 12; + height: parent.height; + + displayName: sendMoneyStep.selectedRecipientDisplayName; + userName: sendMoneyStep.selectedRecipientUserName; + profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? + sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; + isDisplayingNearby: sendMoneyStep.referrer === "nearby"; + } + + // "CHANGE" button + HifiControlsUit.Button { + id: changeButton; + color: hifi.buttons.none; + colorScheme: hifi.colorSchemes.dark; anchors.right: parent.right; - anchors.rightMargin: 20; - height: 80; - - RalewaySemiBold { - id: sendToText_sendMoneyStep; - text: "Send To:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 90; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignVCenter; - } - - RecipientDisplay { - anchors.top: parent.top; - anchors.left: sendToText_sendMoneyStep.right; - anchors.right: changeButton.left; - anchors.rightMargin: 12; - height: parent.height; - - displayName: sendMoneyStep.selectedRecipientDisplayName; - userName: sendMoneyStep.selectedRecipientUserName; - profilePic: sendMoneyStep.selectedRecipientProfilePic !== "" ? ((0 === sendMoneyStep.selectedRecipientProfilePic.indexOf("http")) ? - sendMoneyStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendMoneyStep.selectedRecipientProfilePic)) : ""; - isDisplayingNearby: sendMoneyStep.referrer === "nearby"; - } - - // "CHANGE" button - HifiControlsUit.Button { - id: changeButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - height: 35; - width: 120; - text: "CHANGE"; - onClicked: { - if (sendMoneyStep.referrer === "connections") { - root.nextActiveView = "chooseRecipientConnection"; - } else if (sendMoneyStep.referrer === "nearby") { - root.nextActiveView = "chooseRecipientNearby"; - } - resetSendMoneyData(); + anchors.verticalCenter: parent.verticalCenter; + height: 35; + width: 100; + text: "CHANGE"; + onClicked: { + if (sendMoneyStep.referrer === "connections") { + root.nextActiveView = "chooseRecipientConnection"; + } else if (sendMoneyStep.referrer === "nearby") { + root.nextActiveView = "chooseRecipientNearby"; } + resetSendMoneyData(); + } + } + } + + Item { + id: amountContainer; + anchors.top: sendToContainer.bottom; + anchors.topMargin: 2; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + + RalewaySemiBold { + id: amountText; + text: "Amount:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.TextField { + id: amountTextField; + colorScheme: hifi.colorSchemes.dark; + inputMethodHints: Qt.ImhDigitsOnly; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: amountText.right; + anchors.right: parent.right; + height: 50; + // Style + leftPermanentGlyph: hifi.glyphs.hfc; + activeFocusOnPress: true; + activeFocusOnTab: true; + + validator: IntValidator { bottom: 0; } + + onAccepted: { + optionalMessage.focus = true; } } - Item { - id: amountContainer; - anchors.top: sendToContainer.bottom; + FiraSansSemiBold { + visible: amountTextFieldError.text === ""; + text: "Balance: "; + // Anchors + anchors.top: amountTextField.bottom; anchors.topMargin: 2; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - anchors.rightMargin: 20; - height: 80; - - RalewaySemiBold { - id: amountText; - text: "Amount:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 90; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignVCenter; - } - - HifiControlsUit.TextField { - id: amountTextField; - colorScheme: hifi.colorSchemes.light; - inputMethodHints: Qt.ImhDigitsOnly; - // Anchors - anchors.verticalCenter: parent.verticalCenter; - anchors.left: amountText.right; - anchors.right: parent.right; - height: 50; - // Style - leftPlaceholderGlyph: hifi.glyphs.hfc; - activeFocusOnPress: true; - activeFocusOnTab: true; - - validator: IntValidator { bottom: 0; } - - onAccepted: { - optionalMessage.focus = true; - } - } - - RalewaySemiBold { - id: amountTextFieldError; - // Anchors - anchors.top: amountTextField.bottom; - anchors.topMargin: 2; - anchors.left: amountTextField.left; - anchors.right: amountTextField.right; - height: 40; - // Text size - size: 16; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignTop; - horizontalAlignment: Text.AlignRight; - } + anchors.left: amountTextField.left; + anchors.right: sendMoneyBalanceText_HFC.left; + width: paintedWidth; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + HiFiGlyphs { + id: sendMoneyBalanceText_HFC; + visible: amountTextFieldError.text === ""; + text: hifi.glyphs.hfc; + // Size + size: 16; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.right: sendMoneyBalanceText.left; + width: paintedWidth; + height: 40; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + FiraSansSemiBold { + id: sendMoneyBalanceText; + visible: amountTextFieldError.text === ""; + text: balanceText.text; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.right: amountTextField.right; + width: paintedWidth; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; } - Item { - id: messageContainer; - anchors.top: amountContainer.bottom; - anchors.topMargin: 16; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - anchors.rightMargin: 20; - height: 140; + RalewaySemiBold { + id: amountTextFieldError; + // Anchors + anchors.top: amountTextField.bottom; + anchors.topMargin: 2; + anchors.left: amountTextField.left; + anchors.right: amountTextField.right; + height: 40; + // Text size + size: 16; + // Style + color: hifi.colors.white; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + } + + Item { + id: messageContainer; + anchors.top: amountContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 140; - FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; } - TextArea { - id: optionalMessage; - property int maximumLength: 70; - property string previousText: text; - placeholderText: "Optional Message"; - font.family: firaSansSemiBold.name; - font.pixelSize: 20; - // Anchors + FontLoader { id: firaSansSemiBold; source: "../../../../../fonts/FiraSans-SemiBold.ttf"; } + TextArea { + id: optionalMessage; + property int maximumLength: 72; + property string previousText: text; + placeholderText: "Optional Message (" + maximumLength + " character limit)"; + font.family: firaSansSemiBold.name; + font.pixelSize: 20; + // Anchors + anchors.fill: parent; + // Style + background: Rectangle { anchors.fill: parent; - // Style - background: Rectangle { - anchors.fill: parent; - color: optionalMessage.activeFocus ? hifi.colors.white : hifi.colors.textFieldLightBackground; - border.width: optionalMessage.activeFocus ? 1 : 0; - border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; - } - color: hifi.colors.black; - textFormat: TextEdit.PlainText; - wrapMode: TextEdit.Wrap; - activeFocusOnPress: true; - activeFocusOnTab: true; - // Workaround for no max length on TextAreas - onTextChanged: { - if (text.length > maximumLength) { - var cursor = cursorPosition; - text = previousText; - if (cursor > text.length) { - cursorPosition = text.length; - } else { - cursorPosition = cursor-1; - } - } - previousText = text; - } + color: optionalMessage.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow; + border.width: optionalMessage.activeFocus ? 1 : 0; + border.color: optionalMessage.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; } - RalewaySemiBold { - id: optionalMessageCharacterCount; - text: optionalMessage.text.length + "/" + optionalMessage.maximumLength; - // Anchors - anchors.top: optionalMessage.bottom; - anchors.topMargin: 2; - anchors.right: optionalMessage.right; - height: paintedHeight; - // Text size - size: 16; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignTop; - horizontalAlignment: Text.AlignRight; + color: hifi.colors.white; + textFormat: TextEdit.PlainText; + wrapMode: TextEdit.Wrap; + activeFocusOnPress: true; + activeFocusOnTab: true; + // Workaround for no max length on TextAreas + onTextChanged: { + if (text.length > maximumLength) { + var cursor = cursorPosition; + text = previousText; + if (cursor > text.length) { + cursorPosition = text.length; + } else { + cursorPosition = cursor-1; + } + } + previousText = text; + } + } + FiraSansSemiBold { + id: optionalMessageCharacterCount; + text: optionalMessage.text.length + "/" + optionalMessage.maximumLength; + // Anchors + anchors.top: optionalMessage.bottom; + anchors.topMargin: 4; + anchors.right: optionalMessage.right; + height: paintedHeight; + // Text size + size: 16; + // Style + color: optionalMessage.text.length === optionalMessage.maximumLength ? "#ea89a5" : hifi.colors.lightGrayText; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignRight; + } + } + + HifiControlsUit.CheckBox { + id: sendPubliclyCheckbox; + visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN + text: "Send Publicly" + // Anchors + anchors.top: messageContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 16; + boxSize: 24; + } + + Item { + id: bottomBarContainer; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 20; + height: 60; + + // "CANCEL" button + HifiControlsUit.Button { + id: cancelButton_sendMoneyStep; + color: hifi.buttons.noneBorderlessWhite; + colorScheme: hifi.colorSchemes.dark; + anchors.left: parent.left; + anchors.leftMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: 150; + text: "CANCEL"; + onClicked: { + resetSendMoneyData(); + root.nextActiveView = "sendMoneyHome"; } } - Item { - id: bottomBarContainer; - anchors.top: messageContainer.bottom; - anchors.topMargin: 30; - anchors.left: parent.left; - anchors.leftMargin: 20; + // "SEND" button + HifiControlsUit.Button { + id: sendButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; anchors.right: parent.right; - anchors.rightMargin: 20; - height: 80; - - HifiControlsUit.CheckBox { - id: sendPubliclyCheckbox; - visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN - text: "Send Publicly" - // Anchors - anchors.verticalCenter: parent.verticalCenter; - anchors.left: parent.left; - anchors.right: cancelButton_sendMoneyStep.left; - anchors.rightMargin: 16; - boxSize: 24; - } - - // "CANCEL" button - HifiControlsUit.Button { - id: cancelButton_sendMoneyStep; - color: hifi.buttons.noneBorderless; - colorScheme: hifi.colorSchemes.dark; - anchors.right: sendButton.left; - anchors.rightMargin: 16; - anchors.verticalCenter: parent.verticalCenter; - height: 35; - width: 100; - text: "CANCEL"; - onClicked: { - resetSendMoneyData(); - root.nextActiveView = "sendMoneyHome"; - } - } - - // "SEND" button - HifiControlsUit.Button { - id: sendButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - height: 35; - width: 100; - text: "SEND"; - onClicked: { - if (parseInt(amountTextField.text) > parseInt(balanceText.text)) { - amountTextField.focus = true; - amountTextField.error = true; - amountTextFieldError.text = "amount exceeds available funds"; - } else if (amountTextField.text === "" || parseInt(amountTextField.text) < 1) { - amountTextField.focus = true; - amountTextField.error = true; - amountTextFieldError.text = "invalid amount"; - } else { - amountTextFieldError.text = ""; - amountTextField.error = false; - root.isCurrentlySendingMoney = true; - amountTextField.focus = false; - optionalMessage.focus = false; - if (sendMoneyStep.referrer === "connections") { - Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text); - } else if (sendMoneyStep.referrer === "nearby") { - Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text); - } + anchors.rightMargin: 24; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: 150; + text: "SUBMIT"; + onClicked: { + if (parseInt(amountTextField.text) > parseInt(balanceText.text)) { + amountTextField.focus = true; + amountTextField.error = true; + amountTextFieldError.text = "amount exceeds available funds"; + } else if (amountTextField.text === "" || parseInt(amountTextField.text) < 1) { + amountTextField.focus = true; + amountTextField.error = true; + amountTextFieldError.text = "invalid amount"; + } else { + amountTextFieldError.text = ""; + amountTextField.error = false; + root.isCurrentlySendingMoney = true; + amountTextField.focus = false; + optionalMessage.focus = false; + if (sendMoneyStep.referrer === "connections") { + Commerce.transferHfcToUsername(sendMoneyStep.selectedRecipientUserName, parseInt(amountTextField.text), optionalMessage.text); + } else if (sendMoneyStep.referrer === "nearby") { + Commerce.transferHfcToNode(sendMoneyStep.selectedRecipientNodeID, parseInt(amountTextField.text), optionalMessage.text); } } } @@ -981,7 +1051,7 @@ Item { visible: root.isCurrentlySendingMoney; anchors.fill: parent; - color: Qt.rgba(0.0, 0.0, 0.0, 0.5); + color: Qt.rgba(0.0, 0.0, 0.0, 0.8); // This object is always used in a popup or full-screen Wallet section. // This MouseArea is used to prevent a user from being @@ -993,11 +1063,10 @@ Item { AnimatedImage { id: sendingMoneyImage; - source: "../../../../../icons/profilePicLoading.gif" - width: 160; + source: "./images/loader.gif" + width: 96; height: width; - anchors.top: parent.top; - anchors.topMargin: 185; + anchors.verticalCenter: parent.verticalCenter; anchors.horizontalCenter: parent.horizontalCenter; } @@ -1005,11 +1074,11 @@ Item { text: "Sending"; // Anchors anchors.top: sendingMoneyImage.bottom; - anchors.topMargin: 22; + anchors.topMargin: 4; anchors.horizontalCenter: parent.horizontalCenter; width: paintedWidth; // Text size - size: 24; + size: 26; // Style color: hifi.colors.white; verticalAlignment: Text.AlignVCenter; @@ -1018,17 +1087,17 @@ Item { // Sending Money Overlay END // Payment Success BEGIN - Rectangle { + Item { id: paymentSuccess; visible: root.currentActiveView === "paymentSuccess"; anchors.fill: parent; - color: "#AAAAAA"; Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; RalewaySemiBold { id: paymentSentText; @@ -1049,6 +1118,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentSuccess; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -1100,6 +1170,7 @@ Item { anchors.left: sendToText_paymentSuccess.right; anchors.right: parent.right; height: parent.height; + textColor: hifi.colors.blueAccent; displayName: sendMoneyStep.selectedRecipientDisplayName; userName: sendMoneyStep.selectedRecipientUserName; @@ -1145,10 +1216,10 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: 50; // Style - color: hifi.colors.baseGray; + color: hifi.colors.blueAccent; } - RalewaySemiBold { + FiraSansSemiBold { id: amountSentText; text: amountTextField.text; // Anchors @@ -1159,7 +1230,7 @@ Item { height: 50; // Style size: 22; - color: hifi.colors.baseGray; + color: hifi.colors.blueAccent; } } @@ -1177,7 +1248,7 @@ Item { // Text size size: 22; // Style - color: hifi.colors.baseGray; + color: hifi.colors.blueAccent; wrapMode: Text.Wrap; verticalAlignment: Text.AlignTop; } @@ -1203,17 +1274,17 @@ Item { // Payment Success END // Payment Failure BEGIN - Rectangle { + Item { id: paymentFailure; visible: root.currentActiveView === "paymentFailure"; anchors.fill: parent; - color: "#AAAAAA"; Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; + color: "#FFFFFF"; RalewaySemiBold { id: paymentFailureText; @@ -1234,6 +1305,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentFailure; text: hifi.glyphs.close; + color: hifi.colors.lightGrayText; size: 26; anchors.top: parent.top; anchors.topMargin: 10; @@ -1303,6 +1375,7 @@ Item { anchors.left: sentToText_paymentFailure.right; anchors.right: parent.right; height: parent.height; + textColor: hifi.colors.baseGray; displayName: sendMoneyStep.selectedRecipientDisplayName; userName: sendMoneyStep.selectedRecipientUserName; @@ -1351,7 +1424,7 @@ Item { color: hifi.colors.baseGray; } - RalewaySemiBold { + FiraSansSemiBold { id: amountSentText_paymentFailure; text: amountTextField.text; // Anchors diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg new file mode 100644 index 0000000000..7c5403fda3 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/connection.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif new file mode 100644 index 0000000000..0536bd1884 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif differ diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg new file mode 100644 index 0000000000..dec87e658d --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/nearby.svg @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg new file mode 100644 index 0000000000..59635a99b1 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-selected.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg new file mode 100644 index 0000000000..bba07b9567 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/p2p-nearby-unselected.svg @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4b96bc2f2e..8b1d70e9e2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -209,6 +209,7 @@ #include "commerce/QmlCommerce.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" +#include // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -631,6 +632,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); DependencyManager::set(ScriptEngine::CLIENT_SCRIPT); @@ -5763,6 +5765,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get().data()); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index ac5a467e76..a334834979 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -175,4 +175,8 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const result = pickResult->toVariantMap(); } return result; -} \ No newline at end of file +} + +void PointerScriptingInterface::setDoesHover(unsigned int uid, bool hover) const { + DependencyManager::get()->setDoesHover(uid, hover); +} diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 1cc7b56503..451c132769 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -202,6 +202,14 @@ public: */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); } + /**jsdoc + * Sets whether or not a pointer should generate hover events. + * @function Pointers.setDoesHover + * @param {boolean} uid - The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @param {boolean} hover - If true then the pointer generates hover events, otherwise it does not. + */ + Q_INVOKABLE void setDoesHover(unsigned int uid, bool hove) const; + /**jsdoc * Check if a Pointer is associated with the left hand. * @function Pointers.isLeftHand diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1d7fee38eb..90bb83a663 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,6 +33,7 @@ #include "../Logging.h" #include "../CompositorHelper.h" +#include "DesktopPreviewProvider.h" #include "render-utils/hmd_ui_vert.h" #include "render-utils/hmd_ui_frag.h" @@ -254,17 +255,9 @@ void HmdDisplayPlugin::internalPresent() { swapBuffers(); } else if (_clearPreviewFlag) { - QImage image; - if (_vsyncEnabled) { - image = QImage(PathUtils::resourcesPath() + "images/preview.png"); - } else { - image = QImage(PathUtils::resourcesPath() + "images/preview-disabled.png"); - } - image = image.mirrored(); - image = image.convertToFormat(QImage::Format_RGBA8888); - if (!_previewTexture) { - _previewTexture = gpu::Texture::createStrict( + QImage image = DesktopPreviewProvider::getInstance()->getPreviewDisabledImage(_vsyncEnabled); + _previewTexture = gpu::Texture::createStrict( gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, @@ -274,7 +267,6 @@ void HmdDisplayPlugin::internalPresent() { _previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); _previewTexture->assignStoredMip(0, image.byteCount(), image.constBits()); _previewTexture->setAutoGenerateMips(true); - } auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions())); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 827507c3aa..fc1688974c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1394,8 +1394,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // That is where _currentFrame and _lastAnimated were updated. if (_animating) { DETAILED_PROFILE_RANGE(simulation_physics, "Animate"); + if (!jointsMapped()) { mapJoints(entity, model->getJointNames()); + //else the joint have been mapped before but we have a new animation to load + } else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) { + _animation = DependencyManager::get()->getAnimation(entity->getAnimationURL()); + _jointMappingCompleted = false; + mapJoints(entity, model->getJointNames()); } if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) { animate(entity); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ecfb7b5dcd..f9559a375b 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -368,6 +368,7 @@ public: void* getPhysicsInfo() const { return _physicsInfo; } void setPhysicsInfo(void* data) { _physicsInfo = data; } + EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; virtual SpatialParentTree* getParentTree() const override; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 33ac887f4f..420da5a1e0 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -256,25 +256,32 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { assert(_entity); assert(entityTreeIsLocked()); measureBodyAcceleration(); - bool positionSuccess; - _entity->setWorldPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false); - if (!positionSuccess) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " - "setPosition failed.*"); - qCDebug(physics) << "EntityMotionState::setWorldTransform setPosition failed" << _entity->getID(); + + // If transform or velocities are flagged as dirty it means a network or scripted change + // occured between the beginning and end of the stepSimulation() and we DON'T want to apply + // these physics simulation results. + uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + // flags are clear + _entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation())); + _entity->setWorldVelocity(getBodyLinearVelocity()); + _entity->setWorldAngularVelocity(getBodyAngularVelocity()); + _entity->setLastSimulated(usecTimestampNow()); + } else { + // only set properties NOT flagged + if (!(flags & Simulation::DIRTY_TRANSFORM)) { + _entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation())); + } + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + _entity->setWorldVelocity(getBodyLinearVelocity()); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + _entity->setWorldAngularVelocity(getBodyAngularVelocity()); + } + if (flags != (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + _entity->setLastSimulated(usecTimestampNow()); + } } - bool orientationSuccess; - _entity->setWorldOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false); - if (!orientationSuccess) { - static QString repeatedMessage = - LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform " - "setOrientation failed.*"); - qCDebug(physics) << "EntityMotionState::setWorldTransform setOrientation failed" << _entity->getID(); - } - _entity->setVelocity(getBodyLinearVelocity()); - _entity->setAngularVelocity(getBodyAngularVelocity()); - _entity->setLastSimulated(usecTimestampNow()); if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; @@ -530,9 +537,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (!_body->isActive()) { // make sure all derivatives are zero - _entity->setVelocity(Vectors::ZERO); - _entity->setAngularVelocity(Vectors::ZERO); - _entity->setAcceleration(Vectors::ZERO); + zeroCleanObjectVelocities(); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); @@ -559,9 +564,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - glm::vec3 zero(0.0f); - _entity->setVelocity(zero); - _entity->setAngularVelocity(zero); + zeroCleanObjectVelocities(); } } _numInactiveUpdates = 0; @@ -818,3 +821,22 @@ bool EntityMotionState::shouldBeLocallyOwned() const { void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { _outgoingPriority = glm::max(_outgoingPriority, priority); } + +void EntityMotionState::zeroCleanObjectVelocities() const { + // If transform or velocities are flagged as dirty it means a network or scripted change + // occured between the beginning and end of the stepSimulation() and we DON'T want to apply + // these physics simulation results. + uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES); + if (!flags) { + _entity->setWorldVelocity(glm::vec3(0.0f)); + _entity->setWorldAngularVelocity(glm::vec3(0.0f)); + } else { + if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) { + _entity->setWorldVelocity(glm::vec3(0.0f)); + } + if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) { + _entity->setWorldAngularVelocity(glm::vec3(0.0f)); + } + } + _entity->setAcceleration(glm::vec3(0.0f)); +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index ddfd7e1e4c..784273d600 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -87,6 +87,7 @@ public: protected: // changes _outgoingPriority only if priority is larger void upgradeOutgoingPriority(uint8_t priority); + void zeroCleanObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 5307e17355..287d5a3c97 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -64,6 +64,12 @@ bool Pointer::isMouse() const { return DependencyManager::get()->isMouse(_pickUID); } +void Pointer::setDoesHover(bool doesHover) { + withWriteLock([&] { + _hover = doesHover; + }); +} + void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { @@ -95,7 +101,8 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // Hover events - bool doHover = shouldHover(pickResult); + bool doHover = _hover && shouldHover(pickResult); + Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); @@ -104,7 +111,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin hoveredEvent.setMoveOnHoverLeave(moveOnHoverLeave); // if shouldHover && !_prevDoHover, only send hoverBegin - if (_enabled && _hover && doHover && !_prevDoHover) { + if (_enabled && doHover && !_prevDoHover) { if (hoveredObject.type == ENTITY) { emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == OVERLAY) { @@ -112,7 +119,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } else if (hoveredObject.type == HUD) { emit pointerManager->hoverBeginHUD(hoveredEvent); } - } else if (_enabled && _hover && doHover) { + } else if (_enabled && doHover) { if (hoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { @@ -229,7 +236,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin } // if we disable the pointer or disable hovering, send hoverEnd events after triggerEnd - if (_hover && ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover))) { + if ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover)) { if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == OVERLAY) { diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 3197c80cad..9fd434fb15 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -62,6 +62,8 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) {} + virtual void setDoesHover(bool hover); + void update(unsigned int pointerID); virtual void updateVisuals(const PickResultPointer& pickResult) = 0; void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult); @@ -101,7 +103,6 @@ private: std::unordered_map _triggeredObjects; PointerEvent::Button chooseButton(const std::string& button); - }; #endif // hifi_Pick_h diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index be890da392..13b38457b6 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -122,6 +122,13 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo } } +void PointerManager::setDoesHover(unsigned int uid, bool hover) const { + auto pointer = find(uid); + if (pointer) { + pointer->setDoesHover(hover); + } +} + bool PointerManager::isLeftHand(unsigned int uid) { auto pointer = find(uid); if (pointer) { diff --git a/libraries/pointers/src/PointerManager.h b/libraries/pointers/src/PointerManager.h index b98558622f..2c9a37e129 100644 --- a/libraries/pointers/src/PointerManager.h +++ b/libraries/pointers/src/PointerManager.h @@ -37,6 +37,7 @@ public: void setLength(unsigned int uid, float length) const; void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; + void setDoesHover(unsigned int uid, bool hover) const; void update(); diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index 7d08fdabaf..be8330f198 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -186,7 +186,7 @@ float specularDistribution(SurfaceData surface) { // Add geometric factors G1(n,l) and G1(n,v) float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl); denom *= surface.smithInvG1NdotV * smithInvG1NdotL; - // Don't divide by PI as it will be done later + // Don't divide by PI as this is part of the light normalization factor float power = surface.roughness4 / denom; return power; } @@ -202,12 +202,11 @@ vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) { vec3 specular = fresnelColor * power * angleAttenuation; float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x); - diffuse /= 3.1415926; - // Diffuse is divided by PI but specular isn't because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") - return vec4(specular, diffuse); } @@ -222,9 +221,9 @@ vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) { vec3 specular = vec3(fresnelScalar) * power * angleAttenuation; float diffuse = angleAttenuation * (1.0 - fresnelScalar); - diffuse /= 3.1415926; - // Diffuse is divided by PI but specular isn't because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, diffuse); @@ -239,8 +238,9 @@ vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) { float power = specularDistribution(surface); vec3 specular = fresnelColor * power * angleAttenuation; - // Specular isn't divided by PI because an infinitesimal volume light source - // has a multiplier of PI, says Naty Hoffman. + // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that + // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit + // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, 0.f); diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 973998b927..4f761a4aac 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -253,6 +253,7 @@ glm::vec2 getFacingDir2D(const glm::mat4& m); inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z); } +inline bool isNaN(const glm::mat3& value) { return isNaN(value * glm::vec3(1.0f)); } glm::mat4 orthoInverse(const glm::mat4& m); diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 20a5a76b73..324cee3417 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -464,6 +464,36 @@ glm::vec3 SpatiallyNestable::localToWorldDimensions(const glm::vec3& dimensions, return dimensions; } +void SpatiallyNestable::setWorldTransform(const glm::vec3& position, const glm::quat& orientation) { + // guard against introducing NaN into the transform + if (isNaN(orientation) || isNaN(position)) { + return; + } + + bool changed = false; + bool success = true; + Transform parentTransform = getParentTransform(success); + _transformLock.withWriteLock([&] { + Transform myWorldTransform; + Transform::mult(myWorldTransform, parentTransform, _transform); + if (myWorldTransform.getRotation() != orientation) { + changed = true; + myWorldTransform.setRotation(orientation); + } + if (myWorldTransform.getTranslation() != position) { + changed = true; + myWorldTransform.setTranslation(position); + } + if (changed) { + Transform::inverseMult(_transform, parentTransform, myWorldTransform); + _translationChanged = usecTimestampNow(); + } + }); + if (success && changed) { + locationChanged(false); + } +} + glm::vec3 SpatiallyNestable::getWorldPosition(bool& success) const { return getTransform(success).getTranslation(); } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 2a315e9230..090ca4c266 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -87,6 +87,7 @@ public: virtual Transform getParentTransform(bool& success, int depth = 0) const; + void setWorldTransform(const glm::vec3& position, const glm::quat& orientation); virtual glm::vec3 getWorldPosition(bool& success) const; virtual glm::vec3 getWorldPosition() const; virtual void setWorldPosition(const glm::vec3& position, bool& success, bool tellPhysics = true); diff --git a/libraries/ui/src/DesktopPreviewProvider.cpp b/libraries/ui/src/DesktopPreviewProvider.cpp new file mode 100644 index 0000000000..31aa7a22e2 --- /dev/null +++ b/libraries/ui/src/DesktopPreviewProvider.cpp @@ -0,0 +1,46 @@ +#include "DesktopPreviewProvider.h" +#include +#include +#include +#include + +DesktopPreviewProvider::DesktopPreviewProvider() { +} + +constexpr const char* DesktopPreviewProvider::imagePaths[]; + +QSharedPointer DesktopPreviewProvider::getInstance() { + static QSharedPointer instance = DependencyManager::get(); + return instance; +} + +QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const { + + auto imageIndex = vsyncEnabled ? VSYNC : m_previewDisabledReason; + assert(imageIndex >= 0 && imageIndex <= VSYNC); + + return !m_previewDisabled[imageIndex].isNull() ? m_previewDisabled[imageIndex] : loadPreviewImage(m_previewDisabled[imageIndex], PathUtils::resourcesPath() + imagePaths[imageIndex]); +} + +void DesktopPreviewProvider::setPreviewDisabledReason(PreviewDisabledReasons reason) { + if (reason == VSYNC) { + qDebug() << "Preview disabled reason can't be forced to " << QMetaEnum::fromType().valueToKey(reason); + return; // Not settable via this interface, as VSYNC is controlled by HMD plugin.. + } + + m_previewDisabledReason = reason; +} + +void DesktopPreviewProvider::setPreviewDisabledReason(const QString& reasonString) { + PreviewDisabledReasons reason = USER; + bool ok = false; + + reason = (PreviewDisabledReasons) QMetaEnum::fromType().keyToValue(reasonString.toLatin1().data(), &ok); + if (ok) { + setPreviewDisabledReason(reason); + } +} + +QImage& DesktopPreviewProvider::loadPreviewImage(QImage& image, const QString& path) const { + return image = QImage(path).mirrored().convertToFormat(QImage::Format_RGBA8888); +} diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h new file mode 100644 index 0000000000..449c3723e1 --- /dev/null +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -0,0 +1,47 @@ +// +// Created by Alexander Ivash on 2018/01/08 +// 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 +// + +#include +#include + +class DesktopPreviewProvider : public QObject, public Dependency { + SINGLETON_DEPENDENCY + Q_OBJECT + + DesktopPreviewProvider(); + DesktopPreviewProvider(const DesktopPreviewProvider& other) = delete; + + constexpr static const char* imagePaths[] = { + "images/preview-disabled.png", // USER + "images/preview-privacy.png", // SECURE_SCREEN + "images/preview.png", // VSYNC + }; + +public: + enum PreviewDisabledReasons { + USER = 0, + SECURE_SCREEN, + VSYNC // Not settable via this interface, as VSYNC is controlled by HMD plugin.. + }; + Q_ENUM(PreviewDisabledReasons) + + static QSharedPointer getInstance(); + + QImage getPreviewDisabledImage(bool vsyncEnabled) const; + void setPreviewDisabledReason(PreviewDisabledReasons reason); + +public slots: + void setPreviewDisabledReason(const QString& reason); + +private: + QImage& loadPreviewImage(QImage& image, const QString& path) const; + + PreviewDisabledReasons m_previewDisabledReason = { USER }; + + mutable QImage m_previewDisabled[3]; +}; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index c69ec1ce84..0bc9676a2b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -31,6 +31,8 @@ const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml"; +const QString BUTTON_SORT_ORDER_KEY = "sortOrder"; +const int DEFAULT_BUTTON_SORT_ORDER = 100; static QString getUsername() { QString username = "Unknown user"; @@ -74,11 +76,21 @@ QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { } TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + QVariantMap newProperties = properties.toMap(); + if (newProperties.find(BUTTON_SORT_ORDER_KEY) == newProperties.end()) { + newProperties[BUTTON_SORT_ORDER_KEY] = DEFAULT_BUTTON_SORT_ORDER; + } + int index = computeNewButtonIndex(newProperties); + auto button = QSharedPointer(new TabletButtonProxy(newProperties)); beginResetModel(); - _buttons.push_back(tabletButtonProxy); + int numButtons = (int)_buttons.size(); + if (index < numButtons) { + _buttons.insert(_buttons.begin() + index, button); + } else { + _buttons.push_back(button); + } endResetModel(); - return tabletButtonProxy.data(); + return button.data(); } void TabletButtonListModel::removeButton(TabletButtonProxy* button) { @@ -92,6 +104,20 @@ void TabletButtonListModel::removeButton(TabletButtonProxy* button) { endResetModel(); } +int TabletButtonListModel::computeNewButtonIndex(const QVariantMap& newButtonProperties) { + int numButtons = (int)_buttons.size(); + int newButtonSortOrder = newButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); + if (newButtonSortOrder == DEFAULT_BUTTON_SORT_ORDER) return numButtons; + for (int i = 0; i < numButtons; i++) { + QVariantMap tabletButtonProperties = _buttons[i]->getProperties(); + int tabletButtonSortOrder = tabletButtonProperties[BUTTON_SORT_ORDER_KEY].toInt(); + if (newButtonSortOrder <= tabletButtonSortOrder) { + return i; + } + } + return numButtons; +} + TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 56e3ae257b..34827117f0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -115,6 +115,7 @@ protected: friend class TabletProxy; TabletButtonProxy* addButton(const QVariant& properties); void removeButton(TabletButtonProxy* button); + int computeNewButtonIndex(const QVariantMap& newButtonProperties); using List = std::list>; static QHash _roles; static Qt::ItemFlags _flags; diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index c0b001eab3..e9262c9308 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -22,8 +22,7 @@ var button = tablet.addButton({ icon: Script.resolvePath("dynamicsTests.svg"), - text: "Dynamics", - sortOrder: 15 + text: "Dynamics" }); diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index e70565cec2..c2173f6e2a 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -32,8 +32,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/developer/utilities/render/lod.js b/scripts/developer/utilities/render/lod.js index dc0b99edc2..307e509d39 100644 --- a/scripts/developer/utilities/render/lod.js +++ b/scripts/developer/utilities/render/lod.js @@ -30,8 +30,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js index 1e2ac1261f..6482c884ff 100644 --- a/scripts/developer/utilities/render/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -31,8 +31,7 @@ var button = tablet.addButton({ text: TABLET_BUTTON_NAME, icon: ICON_URL, - activeIcon: ACTIVE_ICON_URL, - sortOrder: 1 + activeIcon: ACTIVE_ICON_URL }); var hasEventBridge = false; diff --git a/scripts/system/chat.js b/scripts/system/chat.js index 0cb414e23c..b0a2e114a3 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -945,8 +945,7 @@ tabletButton = tablet.addButton({ icon: tabletButtonIcon, activeIcon: tabletButtonActiveIcon, - text: tabletButtonName, - sortOrder: 0 + text: tabletButtonName }); Messages.subscribe(channelName); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 0826325a57..ad864622ed 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -562,9 +562,11 @@ break; case 'disableHmdPreview': isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); + DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); break; case 'maybeEnableHmdPreview': + DesktopPreviewProvider.setPreviewDisabledReason("USER"); Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; case 'passphraseReset': @@ -591,10 +593,16 @@ getConnectionData(false); break; case 'enable_ChooseRecipientNearbyMode': - Script.update.connect(updateOverlays); + if (!isUpdateOverlaysWired) { + Script.update.connect(updateOverlays); + isUpdateOverlaysWired = true; + } break; case 'disable_ChooseRecipientNearbyMode': - Script.update.disconnect(updateOverlays); + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } removeOverlays(); break; default: @@ -635,7 +643,11 @@ // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. function onTabletScreenChanged(type, url) { - onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE); + var onWalletScreenNow = (type === "QML" && url === WALLET_QML_SOURCE); + if (!onWalletScreenNow && onWalletScreen) { + DesktopPreviewProvider.setPreviewDisabledReason("USER"); + } + onWalletScreen = onWalletScreenNow; wireEventBridge(onWalletScreen); // Change button to active when window is first openend, false otherwise. if (button) { @@ -675,14 +687,18 @@ } } var isWired = false; + var isUpdateOverlaysWired = 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; } + if (isUpdateOverlaysWired) { + Script.update.disconnect(updateOverlays); + isUpdateOverlaysWired = false; + } triggerMapping.disable(); // It's ok if we disable twice. triggerPressMapping.disable(); // see above removeOverlays(); diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 16f1d086b7..a8658933e7 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -44,7 +44,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.highVarianceCount = 0; this.veryhighVarianceCount = 0; this.tabletID = null; + this.TABLET_UI_UUIDS = []; this.blacklist = []; + this.leftPointerDoesHover = true; + this.leftPointerDoesHoverChanged = false; + this.rightPointerDoesHover = true; + this.rightPointerDoesHoverChanged = false; this.pointerManager = new PointerManager(); // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are @@ -122,6 +127,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return getControllerWorldLocation(Controller.Standard.RightHand, true); }; + this.isTabletID = function (uuid) { + return _this.TABLET_UI_UUIDS.indexOf(uuid) !== -1; + }; + this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); @@ -148,11 +157,35 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.setIgnorePointerItems = function() { if (HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; + this.TABLET_UI_UUIDS = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID]; Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist); Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist); } }; + this.updateDoesHover = function(handLaser, doesHover) { + if (handLaser.doesHover !== undefined) { + if (handLaser.hand === LEFT_HAND && _this.leftPointerDoesHover !== doesHover) { + _this.leftPointerDoesHover = doesHover; + _this.leftPointerDoesHoverChanged = true; + } else if (handLaser.hand === RIGHT_HAND && _this.rightPointerDoesHover !== doesHover) { + _this.rightPointerDoesHover = doesHover; + _this.rightPointerDoesHoverChanged = true; + } + } + } + + this.updateHovering = function () { + if (_this.leftPointerDoesHoverChanged) { + Pointers.setDoesHover(_this.leftPointer, _this.leftPointerDoesHover); + _this.leftPointerDoesHoverChanged = false; + } + if (_this.rightPointerDoesHoverChanged) { + Pointers.setDoesHover(_this.rightPointer, _this.rightPointerDoesHover); + _this.rightPointerDoesHoverChanged = false; + } + }; + this.update = function () { try { _this.updateInternal(); @@ -324,6 +357,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); + _this.updateDoesHover(candidatePlugin.parameters.handLaser, + candidatePlugin.parameters.handLaser.doesHover); if (DEBUG) { print("controllerDispatcher running " + orderedPluginName); } @@ -354,12 +389,15 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.beginProfileRange("dispatch.run." + runningPluginName); } var runningness = plugin.run(controllerData, deltaTime); - if (!runningness.active) { + if (runningness.active) { + _this.updateDoesHover(plugin.parameters.handLaser, plugin.parameters.handLaser.doesHover); + } else { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); _this.pointerManager.makePointerInvisible(plugin.parameters.handLaser); + _this.updateDoesHover(plugin.parameters.handLaser, true); if (DEBUG) { print("controllerDispatcher stopping " + runningPluginName); } @@ -372,6 +410,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } _this.pointerManager.updatePointersRenderState(controllerData.triggerClicks, controllerData.triggerValues); + _this.updateHovering(); + if (PROFILE) { Script.endProfileRange("dispatch.run"); } diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 3d9d7979d5..cc8378af84 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -87,41 +87,89 @@ Script.include("/~/system/libraries/controllers.js"); return MyAvatar.getDominantHand() === "right" ? 1 : 0; }; - this.dominantHandOverride = false; + this.letOtherHandRunFirst = function (controllerData, pointingAt) { + // If both hands are ready to run, let the other hand run first if it is the dominant hand so that it gets the + // highlight. + var isOtherTriggerPressed = controllerData.triggerValues[this.otherHand] > TRIGGER_OFF_VALUE; + var isLetOtherHandRunFirst = !this.getOtherModule().running + && this.getDominantHand() === this.otherHand + && (this.parameters.handLaser.allwaysOn || isOtherTriggerPressed); + if (isLetOtherHandRunFirst) { + var otherHandPointingAt = controllerData.rayPicks[this.otherHand].objectID; + if (this.isTabletID(otherHandPointingAt)) { + otherHandPointingAt = HMD.tabletID; + } + isLetOtherHandRunFirst = pointingAt === otherHandPointingAt; + } + return isLetOtherHandRunFirst; + }; + + this.hoverItem = null; + + this.isTabletID = function (uuid) { + return [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID].indexOf(uuid) !== -1; + }; this.isReady = function(controllerData) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE - && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; - if ((!otherModuleRunning || isTriggerPressed) - && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) { + if (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)) { this.updateAllwaysOn(); - if (isTriggerPressed) { - this.dominantHandOverride = true; // Override dominant hand. - this.getOtherModule().dominantHandOverride = false; - } + + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { - return makeRunningValues(true, [], []); + var pointingAt = controllerData.rayPicks[this.hand].objectID; + if (this.isTabletID(pointingAt)) { + pointingAt = HMD.tabletID; + } + + if (!this.letOtherHandRunFirst(controllerData, pointingAt)) { + + if (pointingAt !== this.getOtherModule().hoverItem) { + this.parameters.handLaser.doesHover = true; + this.hoverItem = pointingAt; + } else { + this.parameters.handLaser.doesHover = false; + this.hoverItem = null; + } + + return makeRunningValues(true, [], []); + } } } + + this.parameters.handLaser.doesHover = false; + this.hoverItem = null; + return makeRunningValues(false, [], []); }; this.run = function(controllerData, deltaTime) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. - otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE - || this.parameters.handLaser.allwaysOn + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + if (!grabModuleNeedsToRun && (isTriggerPressed || this.parameters.handLaser.allwaysOn && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { this.running = true; + + var pointingAt = controllerData.rayPicks[this.hand].objectID; + if (this.isTabletID(pointingAt)) { + pointingAt = HMD.tabletID; + } + + if (pointingAt !== this.getOtherModule().hoverItem || isTriggerPressed) { + this.parameters.handLaser.doesHover = true; + this.hoverItem = pointingAt; + } else { + this.parameters.handLaser.doesHover = false; + this.hoverItem = null; + } + return makeRunningValues(true, [], []); } this.deleteContextOverlay(); this.running = false; - this.dominantHandOverride = false; + + this.parameters.handLaser.doesHover = false; + this.hoverItem = null; + return makeRunningValues(false, [], []); }; } diff --git a/scripts/system/emote.js b/scripts/system/emote.js index f1f739c126..139870fd63 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -43,7 +43,7 @@ var activeTimer = false; // used to cancel active timer if a user plays an amima var activeEmote = false; // to keep track of the currently playing emote button = tablet.addButton({ - //icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this + icon: "icons/tablet-icons/EmoteAppIcon.svg", text: EMOTE_LABEL, sortOrder: EMOTE_APP_SORT_ORDER }); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 082528ffc5..d3848da7d0 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -37,8 +37,7 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", - text: buttonName, - sortOrder: 8 + text: buttonName }); } diff --git a/scripts/system/goto.js b/scripts/system/goto.js index d364bf579e..5cc5bad844 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -41,8 +41,7 @@ if (Settings.getValue("HUDUIEnabled")) { button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", activeIcon: "icons/tablet-icons/goto-a.svg", - text: buttonName, - sortOrder: 8 + text: buttonName }); } diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html index 30ef3e17a1..0a423b9b6c 100644 --- a/scripts/system/html/EmoteApp.html +++ b/scripts/system/html/EmoteApp.html @@ -44,11 +44,11 @@ input[type=button] { font-family: 'Raleway'; font-weight: bold; - font-size: 13px; + font-size: 20px; text-transform: uppercase; vertical-align: top; - height: 28px; - min-width: 120px; + height: 105px; + min-width: 190px; padding: 0px 18px; margin-right: 6px; border-radius: 5px; @@ -98,14 +98,14 @@

Click an emotion to Emote:

-

-

-

-

-

-

-

-

+

+

+

+

+

+

+

+

diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 11c083dacc..728760c1e7 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -539,8 +539,14 @@ return startingUp; } - function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + function onDomainConnectionRefused(reason, reasonCode) { + // the "login error" reason means that the DS couldn't decrypt the username signature + // since this eventually resolves itself for good actors we don't need to show a notification for it + var LOGIN_ERROR_REASON_CODE = 2; + + if (reasonCode != LOGIN_ERROR_REASON_CODE) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + } } function onEditError(msg) { diff --git a/scripts/system/run.js b/scripts/system/run.js index 054cca6d9c..c34271b18d 100644 --- a/scripts/system/run.js +++ b/scripts/system/run.js @@ -10,8 +10,7 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ icon: Script.resolvePath("assets/images/run.svg"), - text: "RUN", - sortOrder: 15 + text: "RUN" }); function onClicked() { diff --git a/scripts/system/tablet-users.js b/scripts/system/tablet-users.js index 6f37cd55eb..6181173818 100644 --- a/scripts/system/tablet-users.js +++ b/scripts/system/tablet-users.js @@ -37,8 +37,7 @@ var button = tablet.addButton({ icon: "icons/tablet-icons/users-i.svg", activeIcon: "icons/tablet-icons/users-a.svg", - text: "USERS", - sortOrder: 11 + text: "USERS" }); var onUsersScreen = false; diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index b2d8ce55db..7118b3623c 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -31,8 +31,7 @@ var activeButton = tablet.addButton({ icon: whiteIcon, activeIcon: blackIcon, text: APP_NAME, - isActive: isActive, - sortOrder: 11 + isActive: isActive }); if (isActive) { diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js index 7bc65722cd..0bd23552e4 100644 --- a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -32,8 +32,7 @@ var button = tablet.addButton({ icon: ICONS.icon, activeIcon: ICONS.activeIcon, - text: TABLET_BUTTON_NAME, - sortOrder: 1 + text: TABLET_BUTTON_NAME }); var hasEventBridge = false;