diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4c1b8d8d92..b3400c8dd0 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -12,8 +12,10 @@ function(JOIN VALUES GLUE OUTPUT) endfunction() -set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) -generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) +if (NOT DEV_BUILD) + set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) + generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) +endif() # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -80,7 +82,9 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") +if (NOT DEV_BUILD) list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC}) +endif() if (UNIX) install( diff --git a/interface/resources/qml/OverlayWindowTest.qml b/interface/resources/qml/OverlayWindowTest.qml new file mode 100644 index 0000000000..7b82b2f705 --- /dev/null +++ b/interface/resources/qml/OverlayWindowTest.qml @@ -0,0 +1,18 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Rectangle { + width: 100 + height: 100 + color: "white" + Rectangle { + width: 10 + height: 10 + color: "red" + } + + Label { + text: OverlayWindowTestString + anchors.centerIn: parent + } +} diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 9a84418b3a..7c1ce704c3 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -22,7 +22,6 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property var source; - property var component; property var dynamicContent; // Keyboard control properties in case needed by QML content. @@ -35,28 +34,9 @@ Windows.Window { dynamicContent.destroy(); dynamicContent = null; } - component = Qt.createComponent(source); - console.log("Created component " + component + " from source " + source); - } - - onComponentChanged: { - console.log("Component changed to " + component) - populate(); - } - - function populate() { - console.log("Populate called: dynamicContent " + dynamicContent + " component " + component); - if (!dynamicContent && component) { - if (component.status == Component.Error) { - console.log("Error loading component:", component.errorString()); - } else if (component.status == Component.Ready) { - console.log("Building dynamic content"); - dynamicContent = component.createObject(contentHolder); - } else { - console.log("Component not yet ready, connecting to status change"); - component.statusChanged.connect(populate); - } - } + QmlSurface.load(source, contentHolder, function(newObject) { + dynamicContent = newObject; + }); } // Handle message traffic from the script that launched us to the loaded QML diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index c068fdcfaf..02c6181952 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -29,12 +29,12 @@ Original.Button { onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index 22b25671c3..e60f646327 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -31,12 +31,12 @@ Original.CheckBox { activeFocusOnPress: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } // TODO: doesnt works for QQC1. check with QQC2 // onHovered: { -// tabletInterface.playSound(TabletEnums.ButtonHover); +// Tablet.playSound(TabletEnums.ButtonHover); // } style: CheckBoxStyle { diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml index 32d69cf339..12b8c80003 100644 --- a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -36,12 +36,12 @@ CheckBox { hoverEnabled: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index bc7bc636fe..9c23171ee1 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -27,12 +27,12 @@ Original.Button { onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } style: ButtonStyle { diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controls-uit/Key.qml index 4ca6cd1b53..b2c720368d 100644 --- a/interface/resources/qml/controls-uit/Key.qml +++ b/interface/resources/qml/controls-uit/Key.qml @@ -41,13 +41,13 @@ Item { onContainsMouseChanged: { if (containsMouse) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { mouse.accepted = true; - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); webEntity.synthesizeKeyPress(glyph); webEntity.synthesizeKeyPress(glyph, mirrorText); diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml index 65d36d2dcb..a818be072b 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -30,12 +30,12 @@ Original.RadioButton { readonly property int checkRadius: 2 onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } // TODO: doesnt works for QQC1. check with QQC2 // onHovered: { -// tabletInterface.playSound(TabletEnums.ButtonHover); +// Tablet.playSound(TabletEnums.ButtonHover); // } style: RadioButtonStyle { diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 6ab4fd9758..efc8519c1e 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -49,7 +49,7 @@ Item { } if (WebEngineView.LoadFailedStatus === loadRequest.status) { - console.log(" Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); + console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString()); } if (WebEngineView.LoadSucceededStatus === loadRequest.status) { diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index 3a5c850031..454a9124ae 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -25,13 +25,13 @@ Preference { id: button onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { preference.trigger(); - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } width: 180 anchors.bottom: parent.bottom diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index 8904896ab7..73819839a1 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -41,12 +41,12 @@ Preference { id: checkBox onHoveredChanged: { if (hovered) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } anchors { diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index fc7b8c6200..83bf1e2c54 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -246,12 +246,12 @@ Item { anchors.fill: parent; acceptedButtons: Qt.LeftButton; onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); goFunction("hifi://" + hifiUrl); } hoverEnabled: true; onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); hoverThunk(); } onExited: unhoverThunk(); @@ -269,7 +269,7 @@ Item { } } function go() { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); } MouseArea { diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 96bb359aea..896b3cf10e 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -45,11 +45,13 @@ OriginalDesktop.Desktop { Toolbar { id: sysToolbar; objectName: "com.highfidelity.interface.toolbar.system"; + property var tablet: Tablet.getTablet("com.highfidelity.interface.tablet.system"); anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. x: sysToolbar.x y: 50 - shown: true + buttonModel: tablet.buttons; + shown: tablet.toolbarMode; } Settings { diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index b6699d6ceb..c66904034b 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -61,12 +61,12 @@ Rectangle { scrollGestureEnabled: false; onClicked: { Audio.muted = !Audio.muted; - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; onContainsMouseChanged: { if (containsMouse) { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); } } } diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index c9e5d2aaec..6b7432b8b2 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -41,10 +41,11 @@ Rectangle { property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property bool isWearable; + property string referrer; // Style color: hifi.colors.white; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -72,7 +73,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -115,7 +116,7 @@ Rectangle { } onItemIdChanged: { - commerce.inventory(); + Commerce.inventory(); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -124,14 +125,14 @@ Rectangle { } onItemPriceChanged: { - commerce.balance(); + Commerce.balance(); } Timer { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId}); + sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId, referrer: referrer}); } } @@ -203,7 +204,7 @@ Rectangle { Component.onCompleted: { purchasesReceived = false; balanceReceived = false; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -224,7 +225,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -474,9 +475,9 @@ Rectangle { if (itemIsJson) { buyButton.enabled = false; if (!root.shouldBuyWithControlledFailure) { - commerce.buy(itemId, itemPrice); + Commerce.buy(itemId, itemPrice); } else { - commerce.buy(itemId, itemPrice, true); + Commerce.buy(itemId, itemPrice, true); } } else { if (urlHandler.canHandleUrl(itemHref)) { @@ -877,6 +878,7 @@ Rectangle { itemName = message.params.itemName; root.itemPrice = message.params.itemPrice; itemHref = message.params.itemHref; + referrer = message.params.referrer; setBuyText(); break; default: @@ -940,8 +942,8 @@ Rectangle { } root.balanceReceived = false; root.purchasesReceived = false; - commerce.inventory(); - commerce.balance(); + Commerce.inventory(); + Commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 8b3f017fd2..a4b0f57e8f 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -31,14 +31,14 @@ Item { height: mainContainer.height + additionalDropdownHeight; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 3) { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } else if (walletStatus > 3) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); } @@ -48,7 +48,7 @@ Item { if (!isLoggedIn) { sendToParent({method: "needsLogIn"}); } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -61,13 +61,13 @@ Item { } Component.onCompleted: { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index d98c453a0c..28c32c59de 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -36,8 +36,8 @@ Rectangle { property bool isCertificateInvalid: false; // Style color: hifi.colors.faintGray; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onCertificateInfoResult: { if (result.status !== 'success') { @@ -109,7 +109,7 @@ Rectangle { onCertificateIdChanged: { if (certificateId !== "") { - commerce.certificateInfo(certificateId); + Commerce.certificateInfo(certificateId); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 5366bda4d6..de66be4a88 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -38,8 +38,8 @@ Rectangle { property bool isDebuggingFirstUseTutorial: false; // Style color: hifi.colors.white; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -61,7 +61,7 @@ Rectangle { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; - commerce.inventory(); + Commerce.inventory(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -72,7 +72,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -198,7 +198,7 @@ Rectangle { Component.onCompleted: { securityImageResultReceived = false; purchasesReceived = false; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -219,7 +219,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -234,7 +234,7 @@ Rectangle { onSendSignalToParent: { if (msg.method === "authSuccess") { root.activeView = "initialize"; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else { sendToScript(msg); } @@ -255,7 +255,7 @@ Rectangle { case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; - commerce.inventory(); + Commerce.inventory(); break; } } @@ -595,7 +595,7 @@ Rectangle { if (root.activeView === "purchasesMain" && !root.pendingInventoryReply) { console.log("Refreshing Purchases..."); root.pendingInventoryReply = true; - commerce.inventory(); + Commerce.inventory(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index ebba2b87c6..409833df98 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -27,8 +27,8 @@ Item { property string keyFilePath; property bool showDebugButtons: true; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onKeyFilePathIfExistsResult: { root.keyFilePath = path; @@ -37,7 +37,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } @@ -55,6 +55,37 @@ Item { // Style color: hifi.colors.blueHighlight; } + HifiControlsUit.Button { + id: clearCachedPassphraseButton; + visible: root.showDebugButtons; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.left: helpTitleText.right; + anchors.leftMargin: 20; + height: 40; + width: 150; + text: "DBG: Clear Pass"; + onClicked: { + Commerce.setPassphrase(""); + sendSignalToWallet({method: 'passphraseReset'}); + } + } + HifiControlsUit.Button { + id: resetButton; + visible: root.showDebugButtons; + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.dark; + anchors.top: clearCachedPassphraseButton.top; + anchors.left: clearCachedPassphraseButton.right; + height: 40; + width: 150; + text: "DBG: RST Wallet"; + onClicked: { + Commerce.reset(); + sendSignalToWallet({method: 'walletReset'}); + } + } ListModel { id: helpModel; diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index 7ce0cf3853..404d7e84cf 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -30,8 +30,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; } // diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index 82262ebf0a..87430246f3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -36,8 +36,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { titleBarSecurityImage.source = ""; @@ -50,9 +50,11 @@ Item { submitPassphraseInputButton.enabled = true; if (!isAuthenticated) { errorText.text = "Authentication failed - please try again."; + passphraseField.error = true; UserActivityLogger.commercePassphraseAuthenticationStatus("auth failure"); } else { sendSignalToParent({method: 'authSuccess'}); + passphraseField.error = false; UserActivityLogger.commercePassphraseAuthenticationStatus("auth success"); } } @@ -72,6 +74,7 @@ Item { // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { + passphraseField.error = false; passphraseField.focus = true; sendSignalToParent({method: 'disableHmdPreview'}); } else { @@ -210,7 +213,7 @@ Item { onAccepted: { submitPassphraseInputButton.enabled = false; - commerce.setPassphrase(passphraseField.text); + Commerce.setPassphrase(passphraseField.text); } } @@ -250,7 +253,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -318,7 +321,7 @@ Item { text: "Submit" onClicked: { submitPassphraseInputButton.enabled = false; - commerce.setPassphrase(passphraseField.text); + Commerce.setPassphrase(passphraseField.text); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 9ba066a9fd..50e58f8cc4 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -36,8 +36,8 @@ Item { propagateComposedEvents: false; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { passphrasePageSecurityImage.source = ""; passphrasePageSecurityImage.source = "image://security/securityImage"; @@ -54,6 +54,9 @@ Item { // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { + passphraseField.error = false; + passphraseFieldAgain.error = false; + currentPassphraseField.error = false; if (root.shouldImmediatelyFocus) { focusFirstTextField(); } @@ -160,7 +163,7 @@ Item { source: "image://security/securityImage"; cache: false; onVisibleChanged: { - commerce.getSecurityImage(); + Commerce.getSecurityImage(); } } Item { @@ -283,7 +286,7 @@ Item { passphraseFieldAgain.error = false; currentPassphraseField.error = false; setErrorText(""); - commerce.changePassphrase(currentPassphraseField.text, passphraseField.text); + Commerce.changePassphrase(currentPassphraseField.text, passphraseField.text); return true; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 485b0ec086..d825196655 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -27,8 +27,8 @@ Item { id: root; property string keyFilePath; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onKeyFilePathIfExistsResult: { root.keyFilePath = path; @@ -234,7 +234,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index 050b60159f..b261743434 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -26,8 +26,8 @@ Item { id: root; property bool justSubmitted: false; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { securityImageChangePageSecurityImage.source = ""; diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml index 75334b1686..11a6c99b0c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml @@ -25,8 +25,8 @@ Item { id: root; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; } // "Unavailable" diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 5d269dc5af..ef2b007dc8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -31,13 +31,15 @@ Rectangle { property bool keyboardRaised: false; property bool isPassword: false; + anchors.fill: (typeof parent === undefined) ? undefined : parent; + Image { anchors.fill: parent; source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onWalletStatusResult: { if (walletStatus === 0) { @@ -47,7 +49,7 @@ Rectangle { } else if (walletStatus === 1) { if (root.activeView !== "walletSetup") { root.activeView = "walletSetup"; - commerce.resetLocalWalletOnly(); + Commerce.resetLocalWalletOnly(); var timestamp = new Date(); walletSetup.startingTimestamp = timestamp; walletSetup.setupAttemptID = generateUUID(); @@ -60,8 +62,10 @@ Rectangle { UserActivityLogger.commercePassphraseEntry("wallet app"); } } else if (walletStatus === 3) { - root.activeView = "walletHome"; - commerce.getSecurityImage(); + if (root.activeView !== "walletSetup") { + root.activeView = "walletHome"; + Commerce.getSecurityImage(); + } } else { console.log("ERROR in Wallet.qml: Unknown wallet status: " + walletStatus); } @@ -71,7 +75,7 @@ Rectangle { if (!isLoggedIn && root.activeView !== "needsLogIn") { root.activeView = "needsLogIn"; } else if (isLoggedIn) { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -177,7 +181,7 @@ Rectangle { if (msg.method === 'walletSetup_finished') { if (msg.referrer === '' || msg.referrer === 'marketplace cta') { root.activeView = "initialize"; - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { sendToScript({method: 'goToPurchases'}); } else { @@ -206,17 +210,19 @@ Rectangle { Connections { onSendSignalToWallet: { - if (msg.method === 'walletSetup_raiseKeyboard') { - root.keyboardRaised = true; - root.isPassword = msg.isPasswordField; - } else if (msg.method === 'walletSetup_lowerKeyboard') { - root.keyboardRaised = false; - } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { - root.activeView = "security"; - } else { - sendToScript(msg); + if (passphraseChange.visible) { + if (msg.method === 'walletSetup_raiseKeyboard') { + root.keyboardRaised = true; + root.isPassword = msg.isPasswordField; + } else if (msg.method === 'walletSetup_lowerKeyboard') { + root.keyboardRaised = false; + } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { + root.activeView = "security"; + } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { + root.activeView = "security"; + } else { + sendToScript(msg); + } } } } @@ -257,7 +263,7 @@ Rectangle { color: hifi.colors.baseGray; Component.onCompleted: { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } } @@ -278,7 +284,7 @@ Rectangle { Connections { target: GlobalServices onMyUsernameChanged: { - commerce.getLoginStatus(); + Commerce.getLoginStatus(); } } @@ -292,7 +298,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === "authSuccess") { - commerce.getWalletStatus(); + Commerce.getWalletStatus(); } else { sendToScript(msg); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index d23079d3f3..5a5eeeb91f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -28,8 +28,8 @@ Item { property bool historyReceived: false; property int pendingCount: 0; - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onBalanceResult : { balanceText.text = result.data.balance; @@ -135,8 +135,8 @@ Item { onVisibleChanged: { if (visible) { historyReceived = false; - commerce.balance(); - commerce.history(); + Commerce.balance(); + Commerce.history(); } else { refreshTimer.stop(); } @@ -165,8 +165,8 @@ Item { interval: 4000; onTriggered: { console.log("Refreshing Wallet Home..."); - commerce.balance(); - commerce.history(); + Commerce.balance(); + Commerce.history(); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 1f9b56ca22..21da38def0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -41,8 +41,8 @@ Item { source: "images/wallet-bg.jpg"; } - Hifi.QmlCommerce { - id: commerce; + Connections { + target: Commerce; onSecurityImageResult: { if (!exists && root.lastPage === "step_2") { @@ -252,7 +252,7 @@ Item { height: 50; text: "Cancel"; onClicked: { - sendSignalToWallet({method: 'walletSetup_cancelClicked'}); + sendSignalToWallet({method: 'walletSetup_cancelClicked', referrer: root.referrer }); } } } @@ -366,7 +366,7 @@ Item { onClicked: { root.lastPage = "step_2"; var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) - commerce.chooseSecurityImage(securityImagePath); + Commerce.chooseSecurityImage(securityImagePath); root.activeView = "step_3"; passphraseSelection.clearPassphraseFields(); } @@ -449,7 +449,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getWalletAuthenticatedStatus(); + Commerce.getWalletAuthenticatedStatus(); } } @@ -535,7 +535,7 @@ Item { onClicked: { if (passphraseSelection.validateAndSubmitPassphrase()) { root.lastPage = "step_3"; - commerce.generateKeyPair(); + Commerce.generateKeyPair(); root.activeView = "step_4"; } } @@ -668,7 +668,7 @@ Item { onVisibleChanged: { if (visible) { - commerce.getKeyFilePathIfExists(); + Commerce.getKeyFilePathIfExists(); } } } diff --git a/interface/resources/qml/hifi/tablet/NewEntityButton.qml b/interface/resources/qml/hifi/tablet/NewEntityButton.qml index 7f838717df..1952ef7ee8 100644 --- a/interface/resources/qml/hifi/tablet/NewEntityButton.qml +++ b/interface/resources/qml/hifi/tablet/NewEntityButton.qml @@ -123,11 +123,11 @@ Item { hoverEnabled: true enabled: true onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); newEntityButton.clicked(); } onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); newEntityButton.state = "hover state"; } onExited: { diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 4555095d57..843f8706ed 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -138,7 +138,6 @@ Item { enabled: true preventStealing: false onClicked: { - console.log("Tablet Button Clicked!"); tablet.setCurrentItemState("base state"); if (tabletButton.inDebugMode) { if (tabletButton.isActive) { @@ -152,14 +151,14 @@ Item { tabletButton.clicked(); if (tabletRoot) { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); } } onEntered: { tablet.setCurrentItemState("base state"); tabletButton.isEntered = true; - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); if (tabletButton.isActive) { tabletButton.state = "hover active state"; diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml new file mode 100644 index 0000000000..3f9451436c --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -0,0 +1,159 @@ +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import QtQuick.Layouts 1.3 + +import "." +import "../../styles-uit" +import "../audio" as HifiAudio + +Item { + id: tablet + objectName: "tablet" + property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + Rectangle { + id: bgTopBar + height: 90 + + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + } + + GradientStop { + position: 1 + color: "#1e1e1e" + } + } + + HifiAudio.MicBar { + anchors { + left: parent.left + leftMargin: 30 + verticalCenter: parent.verticalCenter + } + } + + Item { + width: 150 + height: 50 + anchors.right: parent.right + anchors.rightMargin: 30 + anchors.verticalCenter: parent.verticalCenter + + ColumnLayout { + anchors.fill: parent + + RalewaySemiBold { + text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + font.pixelSize: 20 + color: "#afafaf" + } + + RalewaySemiBold { + visible: Account.loggedIn + height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 + text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" + horizontalAlignment: Text.AlignRight + anchors.right: parent.right + font.pixelSize: 20 + color: "#afafaf" + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (!Account.loggedIn) { + DialogsManager.showLoginDialog() + } else { + Account.logOut() + } + } + } + } + } + + Rectangle { + id: bgMain + clip: true + gradient: Gradient { + GradientStop { + position: 0 + color: "#2b2b2b" + } + + GradientStop { + position: 1 + color: "#0f212e" + } + } + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.top: bgTopBar.bottom + + GridView { + id: flickable + anchors.top: parent.top + anchors.topMargin: 15 + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: cellWidth * 3 + cellHeight: 145 + cellWidth: 145 + model: tabletProxy.buttons + delegate: Item { + width: flickable.cellWidth + height: flickable.cellHeight + property var proxy: modelData + + TabletButton { + id: tabletButton + anchors.centerIn: parent + onClicked: modelData.clicked() + state: wrapper.GridView.isCurrentItem ? "hover state" : "base state" + } + + Connections { + target: modelData; + onPropertiesChanged: { + updateProperties(); + } + } + + Component.onCompleted: updateProperties() + + function updateProperties() { + var keys = Object.keys(modelData.properties).forEach(function (key) { + if (tabletButton[key] !== modelData.properties[key]) { + tabletButton[key] = modelData.properties[key]; + } + }); + } + } + } + } + + Keys.onRightPressed: flickable.moveCurrentIndexRight(); + Keys.onLeftPressed: flickable.moveCurrentIndexLeft(); + Keys.onDownPressed: flickable.moveCurrentIndexDown(); + Keys.onUpPressed: flickable.moveCurrentIndexUp(); + Keys.onReturnPressed: { + if (flickable.currentItem) { + flickable.currentItem.proxy.clicked(); + if (tabletRoot) { + tabletRoot.playButtonClickSound(); + } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 4a4a6b7f87..636ebfe71f 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -77,12 +77,12 @@ FocusScope { anchors.fill: parent hoverEnabled: true onEntered: { - tabletInterface.playSound(TabletEnums.ButtonHover); + Tablet.playSound(TabletEnums.ButtonHover); listView.currentIndex = index } onClicked: { - tabletInterface.playSound(TabletEnums.ButtonClick); + Tablet.playSound(TabletEnums.ButtonClick); root.selected(item); } } diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index a161741049..da544c2114 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -68,37 +68,36 @@ Item { function loadSource(url) { tabletApps.clear(); - loader.source = ""; // make sure we load the qml fresh each time. - loader.source = url; tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); + loader.load(url) } function loadQMLOnTop(url) { tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""}); - loader.source = ""; - loader.source = tabletApps.get(currentApp).appUrl; - if (loader.item.hasOwnProperty("gotoPreviousApp")) { - loader.item.gotoPreviousApp = true; - } + loader.load(tabletApps.get(currentApp).appUrl, function(){ + if (loader.item.hasOwnProperty("gotoPreviousApp")) { + loader.item.gotoPreviousApp = true; + } + }) } - function loadWebOnTop(url, injectJavaScriptUrl) { - tabletApps.append({"appUrl": loader.source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url}); - loader.item.url = tabletApps.get(currentApp).appWebUrl; - loader.item.scriptUrl = tabletApps.get(currentApp).scriptUrl; - if (loader.item.hasOwnProperty("gotoPreviousApp")) { - loader.item.gotoPreviousApp = true; - } + function loadWebContent(source, url, injectJavaScriptUrl) { + tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url}); + loader.load(source, function() { + loader.item.scriptURL = injectJavaScriptUrl; + loader.item.url = url; + if (loader.item.hasOwnProperty("gotoPreviousApp")) { + loader.item.gotoPreviousApp = true; + } + }); } - function loadWebBase() { - loader.source = ""; - loader.source = "TabletWebView.qml"; + function loadWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl); } - function loadTabletWebBase() { - loader.source = ""; - loader.source = "./BlocksWebView.qml"; + function loadTabletWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl); } function returnToPreviousApp() { @@ -110,7 +109,7 @@ Item { loadSource("TabletWebView.qml"); loadWebUrl(webUrl, scriptUrl); } else { - loader.source = tabletApps.get(currentApp).appUrl; + loader.load(tabletApps.get(currentApp).appUrl); } } @@ -173,47 +172,79 @@ Item { } } - Loader { - id: loader - objectName: "loader" - asynchronous: false - - width: parent.width - height: parent.height - - // Hook up callback for clara.io download from the marketplace. - Connections { - id: eventBridgeConnection - target: eventBridge - onWebEventReceived: { - if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); - } - } - } - - onLoaded: { - if (loader.item.hasOwnProperty("sendToScript")) { - loader.item.sendToScript.connect(tabletRoot.sendToScript); - } - if (loader.item.hasOwnProperty("setRootMenu")) { - loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); - } - loader.item.forceActiveFocus(); - - if (openModal) { - openModal.canceled(); - openModal.destroy(); - openModal = null; - } - - if (openBrowser) { - openBrowser.destroy(); - openBrowser = null; + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: eventBridge + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); } } } + Item { + id: loader + objectName: "loader"; + anchors.fill: parent; + property string source: ""; + property var item: null; + signal loaded; + + onWidthChanged: { + if (loader.item) { + loader.item.width = loader.width; + } + } + + onHeightChanged: { + if (loader.item) { + loader.item.height = loader.height; + } + } + + function load(newSource, callback) { + if (loader.source == newSource) { + loader.loaded(); + return; + } + + if (loader.item) { + loader.item.destroy(); + loader.item = null; + } + + QmlSurface.load(newSource, loader, function(newItem) { + loader.item = newItem; + loader.item.width = loader.width; + loader.item.height = loader.height; + loader.loaded(); + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + + if (openModal) { + openModal.canceled(); + openModal.destroy(); + openModal = null; + } + + if (openBrowser) { + openBrowser.destroy(); + openBrowser = null; + } + + if (callback) { + callback(); + } + }); + } + } + width: 480 height: 706 diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index e3170f85ef..9c027308b8 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -59,26 +59,25 @@ Windows.ScrollingWindow { } function loadSource(url) { - loader.source = ""; // make sure we load the qml fresh each time. - loader.source = url; + loader.load(url) } - function loadWebBase() { - loader.source = ""; - loader.source = "WindowWebView.qml"; + function loadWebContent(source, url, injectJavaScriptUrl) { + loader.load(source, function() { + loader.item.scriptURL = injectJavaScriptUrl; + loader.item.url = url; + if (loader.item.hasOwnProperty("closeButtonVisible")) { + loader.item.closeButtonVisible = false; + } + }); } - function loadTabletWebBase() { - loader.source = ""; - loader.source = "./BlocksWebView.qml"; + function loadWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/TabletWebView.qml", url, injectJavaScriptUrl); } - function loadWebUrl(url, injectedJavaScriptUrl) { - loader.item.url = url; - loader.item.scriptURL = injectedJavaScriptUrl; - if (loader.item.hasOwnProperty("closeButtonVisible")) { - loader.item.closeButtonVisible = false; - } + function loadTabletWebBase(url, injectJavaScriptUrl) { + loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl); } // used to send a message from qml to interface script. @@ -111,38 +110,68 @@ Windows.ScrollingWindow { username = newUsername; } - Loader { + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: eventBridge + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); + } + } + } + + Item { id: loader - objectName: "loader" - asynchronous: false + objectName: "loader"; + property string source: ""; + property var item: null; height: pane.scrollHeight width: pane.contentWidth anchors.left: parent.left anchors.top: parent.top - - // Hook up callback for clara.io download from the marketplace. - Connections { - id: eventBridgeConnection - target: eventBridge - onWebEventReceived: { - if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); - } + signal loaded; + + onWidthChanged: { + if (loader.item) { + loader.item.width = loader.width; } } - - onLoaded: { - if (loader.item.hasOwnProperty("sendToScript")) { - loader.item.sendToScript.connect(tabletRoot.sendToScript); + + onHeightChanged: { + if (loader.item) { + loader.item.height = loader.height; } - if (loader.item.hasOwnProperty("setRootMenu")) { - loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + + function load(newSource, callback) { + if (loader.item) { + loader.item.destroy(); + loader.item = null; } - loader.item.forceActiveFocus(); + + QmlSurface.load(newSource, loader, function(newItem) { + loader.item = newItem; + loader.item.width = loader.width; + loader.item.height = loader.height; + loader.loaded(); + if (loader.item.hasOwnProperty("sendToScript")) { + loader.item.sendToScript.connect(tabletRoot.sendToScript); + } + if (loader.item.hasOwnProperty("setRootMenu")) { + loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu); + } + loader.item.forceActiveFocus(); + + if (callback) { + callback(); + } + }); } } + implicitWidth: 480 implicitHeight: 706 } diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 01aa29f665..ff7e835690 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -11,6 +11,8 @@ Window { horizontalSpacers: horizontal verticalSpacers: !horizontal } + property var tabletProxy; + property var buttonModel: ListModel {} hideBackground: true resizable: false destroyOnCloseButton: false @@ -23,24 +25,32 @@ Window { activator: Item {} property bool horizontal: true property real buttonSize: 50; - property var buttons: [] - property var container: horizontal ? row : column Settings { category: "toolbar/" + window.objectName property alias x: window.x property alias y: window.y } + + Component { + id: buttonComponent + ToolbarButton { + id: toolbarButton + property var proxy: modelData; + onClicked: proxy.clicked() + Component.onCompleted: updateProperties() - onHorizontalChanged: { - var newParent = horizontal ? row : column; - for (var i in buttons) { - var child = buttons[i]; - child.parent = newParent; - if (horizontal) { - child.y = 0 - } else { - child.x = 0 + Connections { + target: proxy; + onPropertiesChanged: updateProperties(); + } + + function updateProperties() { + Object.keys(proxy.properties).forEach(function (key) { + if (toolbarButton[key] !== proxy.properties[key]) { + toolbarButton[key] = proxy.properties[key]; + } + }); } } } @@ -52,97 +62,22 @@ Window { Row { id: row + visible: window.horizontal spacing: 6 + Repeater { + model: buttonModel + delegate: buttonComponent + } } - + Column { id: column + visible: !window.horizontal spacing: 6 - } - - Component { id: toolbarButtonBuilder; ToolbarButton { } } - } - - - function findButtonIndex(name) { - if (!name) { - return -1; - } - - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === name) { - return i; + Repeater { + model: buttonModel + delegate: buttonComponent } } - return -1; - } - - function findButton(name) { - var index = findButtonIndex(name); - if (index < 0) { - return; - } - return buttons[index]; - } - - function sortButtons() { - var children = []; - for (var i = 0; i < container.children.length; i++) { - children[i] = container.children[i]; - } - - children.sort(function (a, b) { - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - - container.children = children; - } - - function addButton(properties) { - properties = properties || {} - - // If a name is specified, then check if there's an existing button with that name - // and return it if so. This will allow multiple clients to listen to a single button, - // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - var result = findButton(properties.objectName); - if (result) { - for (var property in properties) { - result[property] = properties[property]; - } - return result; - } - properties.toolbar = this; - properties.opacity = 0; - result = toolbarButtonBuilder.createObject(container, properties); - buttons.push(result); - - result.opacity = 1; - - sortButtons(); - - fadeIn(null); - - return result; - } - - function removeButton(name) { - var index = findButtonIndex(name); - if (index < -1) { - console.warn("Tried to remove non-existent button " + name); - return; - } - - buttons[index].destroy(); - buttons.splice(index, 1); - - if (buttons.length === 0) { - fadeOut(null); - } } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index f27ba6bb24..7781d1140b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -27,7 +27,8 @@ StateImage { property string activeHoverIcon: button.activeIcon property int sortOrder: 100 - property int stableSortOrder: 0 + property int stableOrder: 0 + property var uuid; signal clicked() diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e93a17553e..f17e06cb35 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2248,27 +2248,65 @@ extern void setupPreferences(); void Application::initializeUi() { // Make sure all QML surfaces share the main thread GL context OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext()); + OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "OverlayWindowTest.qml" }, + [](QQmlContext* context) { + qDebug() << "Whitelist OverlayWindow worked"; + context->setContextProperty("OverlayWindowTestString", "TestWorked"); + }); + OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "hifi/audio/Audio.qml" }, + [](QQmlContext* context) { + qDebug() << "QQQ" << __FUNCTION__ << "Whitelist Audio worked"; + }); + AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); - QmlCommerce::registerType(); + QmlContextCallback callback = [](QQmlContext* context) { + context->setContextProperty("Commerce", new QmlCommerce()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/checkout/Checkout.qml" }, + QUrl{ "hifi/commerce/common/CommerceLightbox.qml" }, + QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" }, + QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" }, + QUrl{ "hifi/commerce/common/SortableListModel.qml" }, + QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" }, + QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" }, + QUrl{ "hifi/commerce/purchases/Purchases.qml" }, + QUrl{ "hifi/commerce/wallet/Help.qml" }, + QUrl{ "hifi/commerce/wallet/NeedsLogIn.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseChange.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseModal.qml" }, + QUrl{ "hifi/commerce/wallet/PassphraseSelection.qml" }, + QUrl{ "hifi/commerce/wallet/Security.qml" }, + 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/Wallet.qml" }, + QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, + QUrl{ "hifi/commerce/wallet/WalletSetup.qml" }, + }, callback); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); qmlRegisterType("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine"); + { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->getTablet(SYSTEM_TABLET); + } auto offscreenUi = DependencyManager::get(); offscreenUi->create(); auto surfaceContext = offscreenUi->getSurfaceContext(); offscreenUi->setProxyWindow(_window->windowHandle()); - offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // support the window management and scripting proxies for VR use - offscreenUi->createDesktop(QString("qrc:///qml/hifi/Desktop.qml")); + offscreenUi->createDesktop(QString("hifi/Desktop.qml")); // FIXME either expose so that dialogs can set this themselves or // do better detection in the offscreen UI of what has focus @@ -2336,9 +2374,6 @@ void Application::initializeUi() { surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); - surfaceContext->setContextProperty("Tablet", DependencyManager::get().data()); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); surfaceContext->setContextProperty("FaceTracker", DependencyManager::get().data()); @@ -4288,9 +4323,11 @@ void Application::updateLOD(float deltaTime) const { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!isThrottleRendering()) { - float batchTime = (float)_gpuContext->getFrameTimerBatchAverage(); + float presentTime = getActiveDisplayPlugin()->getAveragePresentTime(); float engineRunTime = (float)(_renderEngine->getConfiguration().get()->getCPURunTime()); - DependencyManager::get()->autoAdjustLOD(batchTime, engineRunTime, deltaTime); + float gpuTime = getGPUContext()->getFrameTimerGPUAverage(); + float maxRenderTime = glm::max(gpuTime, glm::max(presentTime, engineRunTime)); + DependencyManager::get()->autoAdjustLOD(maxRenderTime, deltaTime); } else { DependencyManager::get()->resetLODAdjust(); } @@ -5836,9 +5873,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Tablet", DependencyManager::get().data()); + // FIXME remove these deprecated names for the tablet scripting interface + scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get().data()); auto toolbarScriptingInterface = DependencyManager::get().data(); DependencyManager::get().data()->setToolbarScriptingInterface(toolbarScriptingInterface); @@ -7245,13 +7282,17 @@ void Application::updateDisplayMode() { } auto offscreenUi = DependencyManager::get(); + auto desktop = offscreenUi->getDesktop(); // Make the switch atomic from the perspective of other threads { std::unique_lock lock(_displayPluginLock); - // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. - bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); - offscreenUi->getDesktop()->setProperty("repositionLocked", true); + bool wasRepositionLocked = false; + if (desktop) { + // Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below. + wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool(); + offscreenUi->getDesktop()->setProperty("repositionLocked", true); + } if (_displayPlugin) { disconnect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent); @@ -7297,7 +7338,6 @@ void Application::updateDisplayMode() { getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); _displayPlugin = newDisplayPlugin; connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection); - auto desktop = offscreenUi->getDesktop(); if (desktop) { desktop->setProperty("repositionLocked", wasRepositionLocked); } diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 01ccbd0d9a..9e6fabd439 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -19,6 +19,7 @@ #include "LODManager.h" + Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); @@ -39,156 +40,95 @@ float LODManager::getLODIncreaseFPS() { return getDesktopLODIncreaseFPS(); } -void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec) { +// We use a "time-weighted running average" of the renderTime and compare it against min/max thresholds +// to determine if we should adjust the level of detail (LOD). +// +// A time-weighted running average has a timescale which determines how fast the average tracks the measured +// value in real-time. Given a step-function in the mesured value, and assuming measurements happen +// faster than the runningAverage is computed, the error between the value and its runningAverage will be +// reduced by 1/e every timescale of real-time that passes. +const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.1f; // sec +// +// Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its +// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage settle +// to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few +// multiples of the running average timescale: +const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec - // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't - // really want to count them in our average, so we will ignore the real frame rates and stuff - // our moving average with simulated good data - const int IGNORE_THESE_SAMPLES = 100; - if (_fpsAverageUpWindow.getSampleCount() < IGNORE_THESE_SAMPLES) { - _lastStable = _lastUpShift = _lastDownShift = usecTimestampNow(); - } +const float LOD_AUTO_ADJUST_DECREMENT_FACTOR = 0.8f; +const float LOD_AUTO_ADJUST_INCREMENT_FACTOR = 1.2f; +void LODManager::autoAdjustLOD(float renderTime, float realTimeDelta) { // compute time-weighted running average renderTime - const float OVERLAY_AND_SWAP_TIME_BUDGET = 2.0f; // msec - float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET; - float maxTime = glm::max(renderTime, engineRunTime); - const float BLEND_TIMESCALE = 0.3f; // sec - const float MIN_DELTA_TIME = 0.001f; - const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME); - float blend = BLEND_TIMESCALE / safeDeltaTime; - if (blend > 1.0f) { - blend = 1.0f; + // Note: we MUST clamp the blend to 1.0 for stability + float blend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f; + _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * renderTime; // msec + if (!_automaticLODAdjust) { + // early exit + return; } - _avgRenderTime = (1.0f - blend) * _avgRenderTime + blend * maxTime; // msec - // translate into fps for legacy implementation + float oldOctreeSizeScale = _octreeSizeScale; float currentFPS = (float)MSECS_PER_SECOND / _avgRenderTime; - - _fpsAverageStartWindow.updateAverage(currentFPS); - _fpsAverageDownWindow.updateAverage(currentFPS); - _fpsAverageUpWindow.updateAverage(currentFPS); - - quint64 now = usecTimestampNow(); - - quint64 elapsedSinceDownShift = now - _lastDownShift; - quint64 elapsedSinceUpShift = now - _lastUpShift; - - quint64 lastStableOrUpshift = glm::max(_lastUpShift, _lastStable); - quint64 elapsedSinceStableOrUpShift = now - lastStableOrUpshift; - - if (_automaticLODAdjust) { - bool changed = false; - - // LOD Downward adjustment - // If we've been downshifting, we watch a shorter downshift window so that we will quickly move toward our - // target frame rate. But if we haven't just done a downshift (either because our last shift was an upshift, - // or because we've just started out) then we look at a much longer window to consider whether or not to start - // downshifting. - bool doDownShift = false; - - if (_isDownshifting) { - // only consider things if our DOWN_SHIFT time has elapsed... - if (elapsedSinceDownShift > DOWN_SHIFT_ELPASED) { - doDownShift = _fpsAverageDownWindow.getAverage() < getLODDecreaseFPS(); - - if (!doDownShift) { - qCDebug(interfaceapp) << "---- WE APPEAR TO BE DONE DOWN SHIFTING -----"; - _isDownshifting = false; - _lastStable = now; - } - } - } else { - doDownShift = (elapsedSinceStableOrUpShift > START_SHIFT_ELPASED - && _fpsAverageStartWindow.getAverage() < getLODDecreaseFPS()); - } - - if (doDownShift) { - - // Octree items... stepwise adjustment + uint64_t now = usecTimestampNow(); + if (currentFPS < getLODDecreaseFPS()) { + if (now > _decreaseFPSExpiry) { + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale *= ADJUST_LOD_DOWN_BY; + _octreeSizeScale *= LOD_AUTO_ADJUST_DECREMENT_FACTOR; if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } - changed = true; - } - - if (changed) { - if (_isDownshifting) { - // subsequent downshift - qCDebug(interfaceapp) << "adjusting LOD DOWN..." - << "average fps for last "<< DOWN_SHIFT_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageDownWindow.getAverage() - << "minimum is:" << getLODDecreaseFPS() - << "elapsedSinceDownShift:" << elapsedSinceDownShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - } else { - // first downshift - qCDebug(interfaceapp) << "adjusting LOD DOWN after initial delay..." - << "average fps for last "<< START_DELAY_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageStartWindow.getAverage() - << "minimum is:" << getLODDecreaseFPS() - << "elapsedSinceUpShift:" << elapsedSinceUpShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - } - - _lastDownShift = now; - _isDownshifting = true; - + qCDebug(interfaceapp) << "adjusting LOD DOWN" + << "fps =" << currentFPS + << "targetFPS =" << getLODDecreaseFPS() + << "octreeSizeScale =" << _octreeSizeScale; emit LODDecreased(); } - } else { - - // LOD Upward adjustment - if (elapsedSinceUpShift > UP_SHIFT_ELPASED) { - - if (_fpsAverageUpWindow.getAverage() > getLODIncreaseFPS()) { - - // Octee items... stepwise adjustment - if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { - if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; - } else { - _octreeSizeScale *= ADJUST_LOD_UP_BY; - } - if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { - _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; - } - changed = true; - } - } - - if (changed) { - qCDebug(interfaceapp) << "adjusting LOD UP... average fps for last "<< UP_SHIFT_WINDOW_IN_SECS <<"seconds was " - << _fpsAverageUpWindow.getAverage() - << "upshift point is:" << getLODIncreaseFPS() - << "elapsedSinceUpShift:" << elapsedSinceUpShift - << " NEW _octreeSizeScale=" << _octreeSizeScale; - - _lastUpShift = now; - _isDownshifting = false; - - emit LODIncreased(); - } - } + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } - - if (changed) { - auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); - if (lodToolsDialog) { - lodToolsDialog->reloadSliders(); + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + } else if (currentFPS > getLODIncreaseFPS()) { + if (now > _increaseFPSExpiry) { + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { + if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; + } else { + _octreeSizeScale *= LOD_AUTO_ADJUST_INCREMENT_FACTOR; + } + if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { + _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; + } + qCDebug(interfaceapp) << "adjusting LOD UP" + << "fps =" << currentFPS + << "targetFPS =" << getLODDecreaseFPS() + << "octreeSizeScale =" << _octreeSizeScale; + emit LODIncreased(); } + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; } + _decreaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + } else { + _increaseFPSExpiry = now + LOD_AUTO_ADJUST_PERIOD; + _decreaseFPSExpiry = _increaseFPSExpiry; + } + + if (oldOctreeSizeScale != _octreeSizeScale) { + auto lodToolsDialog = DependencyManager::get()->getLodToolsDialog(); + if (lodToolsDialog) { + lodToolsDialog->reloadSliders(); + } + // Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime + // to be at middle of target zone. It will drift close to its true value within + // about three few LOD_ADJUST_TIMESCALEs and we'll adjust again as necessary. + float expectedFPS = 0.5f * (getLODIncreaseFPS() + getLODDecreaseFPS()); + _avgRenderTime = MSECS_PER_SECOND / expectedFPS; } } void LODManager::resetLODAdjust() { - _fpsAverageStartWindow.reset(); - _fpsAverageDownWindow.reset(); - _fpsAverageUpWindow.reset(); - _lastUpShift = _lastDownShift = usecTimestampNow(); - _isDownshifting = false; + _decreaseFPSExpiry = _increaseFPSExpiry = usecTimestampNow() + LOD_AUTO_ADJUST_PERIOD; } const float MIN_DECREASE_FPS = 0.5f; @@ -206,7 +146,7 @@ float LODManager::getDesktopLODDecreaseFPS() const { } float LODManager::getDesktopLODIncreaseFPS() const { - return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_DESKTOP_FPS); + return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS); } void LODManager::setHMDLODDecreaseFPS(float fps) { @@ -222,7 +162,7 @@ float LODManager::getHMDLODDecreaseFPS() const { } float LODManager::getHMDLODIncreaseFPS() const { - return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP, MAX_LIKELY_HMD_FPS); + return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS); } QString LODManager::getLODFeedbackText() { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 1b3797a0ca..cf38342db0 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -19,29 +19,13 @@ #include #include -const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 20.0; -const float DEFAULT_HMD_LOD_DOWN_FPS = 20.0; +const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f; +const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f; const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec -const float MAX_LIKELY_DESKTOP_FPS = 59.0; // this is essentially, V-synch - 1 fps -const float MAX_LIKELY_HMD_FPS = 74.0; // this is essentially, V-synch - 1 fps -const float INCREASE_LOD_GAP = 15.0f; - -const float START_DELAY_WINDOW_IN_SECS = 3.0f; // wait at least this long after steady state/last upshift to consider downshifts -const float DOWN_SHIFT_WINDOW_IN_SECS = 0.5f; -const float UP_SHIFT_WINDOW_IN_SECS = 2.5f; - -const int ASSUMED_FPS = 60; -const quint64 START_SHIFT_ELPASED = USECS_PER_SECOND * START_DELAY_WINDOW_IN_SECS; -const quint64 DOWN_SHIFT_ELPASED = USECS_PER_SECOND * DOWN_SHIFT_WINDOW_IN_SECS; // Consider adjusting LOD down after half a second -const quint64 UP_SHIFT_ELPASED = USECS_PER_SECOND * UP_SHIFT_WINDOW_IN_SECS; - -const int START_DELAY_SAMPLES_OF_FRAMES = ASSUMED_FPS * START_DELAY_WINDOW_IN_SECS; -const int DOWN_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * DOWN_SHIFT_WINDOW_IN_SECS; -const int UP_SHIFT_SAMPLES_OF_FRAMES = ASSUMED_FPS * UP_SHIFT_WINDOW_IN_SECS; - -const float ADJUST_LOD_DOWN_BY = 0.9f; -const float ADJUST_LOD_UP_BY = 1.1f; +const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps +const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps +const float INCREASE_LOD_GAP_FPS = 15.0f; // fps // The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision). const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; @@ -78,7 +62,7 @@ public: Q_INVOKABLE float getLODIncreaseFPS(); static bool shouldRender(const RenderArgs* args, const AABox& bounds); - void autoAdjustLOD(float batchTime, float engineRunTime, float deltaTimeSec); + void autoAdjustLOD(float renderTime, float realTimeDelta); void loadSettings(); void saveSettings(); @@ -92,21 +76,15 @@ private: LODManager(); bool _automaticLODAdjust = true; - float _avgRenderTime { 0.0 }; + float _avgRenderTime { 0.0f }; float _desktopMaxRenderTime { DEFAULT_DESKTOP_MAX_RENDER_TIME }; float _hmdMaxRenderTime { DEFAULT_HMD_MAX_RENDER_TIME }; float _octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE; int _boundaryLevelAdjust = 0; - - quint64 _lastDownShift = 0; - quint64 _lastUpShift = 0; - quint64 _lastStable = 0; - bool _isDownshifting = false; // start out as if we're not downshifting - - SimpleMovingAverage _fpsAverageStartWindow = START_DELAY_SAMPLES_OF_FRAMES; - SimpleMovingAverage _fpsAverageDownWindow = DOWN_SHIFT_SAMPLES_OF_FRAMES; - SimpleMovingAverage _fpsAverageUpWindow = UP_SHIFT_SAMPLES_OF_FRAMES; + + uint64_t _decreaseFPSExpiry { 0 }; + uint64_t _increaseFPSExpiry { 0 }; }; #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2d15134b70..eeda9e9036 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -101,7 +101,7 @@ Menu::Menu() { auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J); connect(action, &QAction::triggered, [] { static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml"); - static const QUrl tabletUrl("../../hifi/dialogs/TabletRunningScripts.qml"); + static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml"); static const QString name("RunningScripts"); qApp->showDialog(widgetUrl, tabletUrl, name); }); @@ -338,7 +338,7 @@ Menu::Menu() { connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); - tablet->loadQMLSource("ControllerSettings.qml"); + tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml"); if (!hmd->getShouldShowTablet()) { hmd->toggleShouldShowTablet(); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 69089df9c2..62e87f9c66 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -16,9 +16,7 @@ #include "Wallet.h" #include -HIFI_QML_DEF(QmlCommerce) - -QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { +QmlCommerce::QmlCommerce() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index fd8dd0d395..c53e73d565 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -16,16 +16,14 @@ #define hifi_QmlCommerce_h #include -#include #include -class QmlCommerce : public OffscreenQmlDialog { +class QmlCommerce : public QObject { Q_OBJECT - HIFI_QML_DECL public: - QmlCommerce(QQuickItem* parent = nullptr); + QmlCommerce(); signals: void walletStatusResult(uint walletStatus); diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index f216bb4edc..ff2d4868df 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -31,7 +31,7 @@ #include "scripting/HMDScriptingInterface.h" -static const QVariant TABLET_ADDRESS_DIALOG = "TabletAddressDialog.qml"; +static const QVariant TABLET_ADDRESS_DIALOG = "hifi/tablet/TabletAddressDialog.qml"; template void DialogsManager::maybeCreateDialog(QPointer& member) { if (!member) { @@ -91,7 +91,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { ConnectionFailureDialog::hide(); } } else { - static const QUrl url("../../dialogs/TabletConnectionFailureDialog.qml"); + static const QUrl url("dialogs/TabletConnectionFailureDialog.qml"); auto hmd = DependencyManager::get(); if (visible) { tablet->initialScreen(url); diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 7ce2a0146d..2e40d3c087 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -46,7 +46,7 @@ void LoginDialog::showWithSelection() if (tablet->getToolbarMode()) { LoginDialog::show(); } else { - static const QUrl url("../../dialogs/TabletLoginDialog.qml"); + static const QUrl url("dialogs/TabletLoginDialog.qml"); tablet->initialScreen(url); if (!hmd->getShouldShowTablet()) { hmd->openTablet(); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index c41663fcc9..16b771567c 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -266,7 +266,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -static const QString INSPECTION_CERTIFICATE_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; +static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; void ContextOverlayInterface::openInspectionCertificate() { // lets open the tablet to the inspection certificate QML if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 71d1c4b842..fcbc51b541 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -200,7 +200,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); _webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); @@ -220,9 +219,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); - // Tablet inteference with Tablet.qml. Need to avoid this in QML space - _webSurface->getSurfaceContext()->setContextProperty("tabletInterface", DependencyManager::get().data()); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); // mark the TabletProxy object as cpp ownership. QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 0993daaa8b..e646ba27f5 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -679,17 +679,13 @@ void OpenGLDisplayPlugin::internalPresent() { void OpenGLDisplayPlugin::present() { auto frameId = (uint64_t)presentCount(); PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId) + uint64_t startPresent = usecTimestampNow(); { PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId) updateFrameData(); } incrementPresentCount(); - { - PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, frameId) - _gpuContext->recycle(); - } - if (_currentFrame) { { withPresentThreadLock([&] { @@ -718,6 +714,7 @@ void OpenGLDisplayPlugin::present() { gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory()); } + _movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent)); } float OpenGLDisplayPlugin::newFramePresentRate() const { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e578e4858d..bc99522d0f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -993,19 +993,15 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { } { - // the current frame is set on the server in update() in ModelEntityItem.cpp - int animationCurrentFrame = (int)(glm::floor(entity->getAnimationCurrentFrame())); - - // in the case where the last frame is greater than the framecount then clamp - // it to the end of the animation until it loops around. - if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { - animationCurrentFrame = 0; + float currentFrame = fmod(entity->getAnimationCurrentFrame(), (float)(frameCount)); + if (currentFrame < 0.0f) { + currentFrame += (float)frameCount; } - - if (animationCurrentFrame == _lastKnownCurrentFrame) { + int currentIntegerFrame = (int)(glm::floor(currentFrame)); + if (currentIntegerFrame == _lastKnownCurrentFrame) { return; } - _lastKnownCurrentFrame = animationCurrentFrame; + _lastKnownCurrentFrame = currentIntegerFrame; } if (_jointMapping.size() != _model->getJointStateCount()) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cdd260e73c..16e2c4c789 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -30,6 +30,8 @@ using namespace render; using namespace render::entities; +static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; + const float METERS_TO_INCHES = 39.3701f; static uint32_t _currentWebCount{ 0 }; // Don't allow more than 100 concurrent web views @@ -218,6 +220,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }; { + // FIXME use the surface cache instead of explicit creation _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); _webSurface->create(); } @@ -291,7 +294,6 @@ void WebEntityRenderer::loadSourceURL() { if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { _contentType = htmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { @@ -300,12 +302,11 @@ void WebEntityRenderer::loadSourceURL() { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } - _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { item->setProperty("url", _lastSourceUrl); }); } else { _contentType = qmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); _webSurface->load(_lastSourceUrl); if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 323584c7ee..3a79df34c6 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -245,7 +245,7 @@ void ModelEntityItem::updateFrameCount() { if (_currentFrame < 0.0f) { return; } - + if (!_lastAnimated) { _lastAnimated = usecTimestampNow(); return; @@ -263,7 +263,7 @@ void ModelEntityItem::updateFrameCount() { } int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1; - + if (!getAnimationHold() && getAnimationIsPlaying()) { float deltaTime = (float)interval / (float)USECS_PER_SECOND; _currentFrame += (deltaTime * getAnimationFPS()); @@ -283,8 +283,6 @@ void ModelEntityItem::updateFrameCount() { // qCDebug(entities) << "in update frame " << _currentFrame; setAnimationCurrentFrame(_currentFrame); } - - } void ModelEntityItem::debugDump() const { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 6fb0d7b152..f93d430152 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -772,7 +772,7 @@ void GLBackend::recycle() const { GLVariableAllocationSupport::manageMemory(); GLVariableAllocationSupport::_frameTexturesCreated = 0; - + Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 1877b494cf..06208179e0 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -321,13 +321,18 @@ public: void reset() override { } + // Don't keep files open forever. We close them at the beginning of each frame (GLBackend::recycle) + static void releaseOpenKtxFiles(); + protected: std::shared_ptr maybeOpenFile() const; - mutable std::mutex _cacheFileCreateMutex; - mutable std::mutex _cacheFileWriteMutex; + mutable std::shared_ptr _cacheFileMutex { std::make_shared() }; mutable std::weak_ptr _cacheFile; + static std::vector, std::shared_ptr>> _cachedKtxFiles; + static std::mutex _cachedKtxFilesMutex; + std::string _filename; cache::FilePointer _cacheEntry; std::atomic _minMipLevelAvailable; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 08fc4ec101..883d9abf15 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -23,6 +23,9 @@ using namespace gpu; using PixelsPointer = Texture::PixelsPointer; using KtxStorage = Texture::KtxStorage; +std::vector, std::shared_ptr>> KtxStorage::_cachedKtxFiles; +std::mutex KtxStorage::_cachedKtxFilesMutex; + struct GPUKTXPayload { using Version = uint8; @@ -187,36 +190,44 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { } } +// maybeOpenFile should be called with _cacheFileMutex already held to avoid modifying the file from multiple threads std::shared_ptr KtxStorage::maybeOpenFile() const { - // 1. Try to get the shared ptr - // 2. If it doesn't exist, grab the mutex around its creation - // 3. If it was created before we got the mutex, return it - // 4. Otherwise, create it - + // Try to get the shared_ptr std::shared_ptr file = _cacheFile.lock(); if (file) { return file; } + // If the file isn't open, create it and save a weak_ptr to it + file = std::make_shared(_filename.c_str()); + _cacheFile = file; + { - std::lock_guard lock{ _cacheFileCreateMutex }; - - file = _cacheFile.lock(); - if (file) { - return file; - } - - file = std::make_shared(_filename.c_str()); - _cacheFile = file; + // Add the shared_ptr to the global list of open KTX files, to be released at the beginning of the next present thread frame + std::lock_guard lock(_cachedKtxFilesMutex); + _cachedKtxFiles.emplace_back(file, _cacheFileMutex); } return file; } +void KtxStorage::releaseOpenKtxFiles() { + std::vector, std::shared_ptr>> localKtxFiles; + { + std::lock_guard lock(_cachedKtxFilesMutex); + localKtxFiles.swap(_cachedKtxFiles); + } + for (auto& cacheFileAndMutex : localKtxFiles) { + std::lock_guard lock(*(cacheFileAndMutex.second)); + cacheFileAndMutex.first.reset(); + } +} + PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face); auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); if (faceSize != 0 && faceOffset != 0) { + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (file) { auto storageView = file->createView(faceSize, faceOffset); @@ -262,6 +273,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor return; } + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (!file) { qWarning() << "Failed to open file to assign mip data " << QString::fromStdString(_filename); @@ -279,8 +291,6 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor imageData += ktx::IMAGE_SIZE_WIDTH; { - std::lock_guard lock { _cacheFileWriteMutex }; - if (level != _minMipLevelAvailable - 1) { qWarning() << "Invalid level to be stored"; return; diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index ad650cf067..6c2471f680 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -177,9 +177,10 @@ void Midi::MidiCleanup() { #endif void Midi::noteReceived(int status, int note, int velocity) { - if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && - ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON)) { - return; // NOTE: only sending note-on and note-off to Javascript + if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && + ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) && + ((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) { + return; // NOTE: only sending note-on, note-off, and control-change to Javascript } QVariantMap eventData; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index b8f81dc7a4..8153dba3a5 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -217,8 +217,6 @@ gpu::TexturePointer TextureCache::cacheTextureByHash(const std::string& hash, co if (!result) { _texturesByHashes[hash] = texture; result = texture; - } else { - qCWarning(modelnetworking) << "QQQ Swapping out texture with previous live texture in hash " << hash.c_str(); } } return result; diff --git a/libraries/model/src/model/Haze.h b/libraries/model/src/model/Haze.h index a0cc7c3bc7..2a575eb151 100644 --- a/libraries/model/src/model/Haze.h +++ b/libraries/model/src/model/Haze.h @@ -128,7 +128,7 @@ namespace model { Parameters() {} }; - UniformBufferView _hazeParametersBuffer; + UniformBufferView _hazeParametersBuffer { nullptr }; }; using HazePointer = std::shared_ptr; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 7563122290..3190f25d7e 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifdef _WIN32 -#define _USE_MATH_DEFINES -#endif - #include #include #include diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index efce158864..2c717f629c 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -21,8 +21,10 @@ #include #include +#include #include #include +#include #include #include "Plugin.h" @@ -203,6 +205,7 @@ public: virtual void cycleDebugOutput() {} void waitForPresent(); + float getAveragePresentTime() { return _movingAveragePresent.average / (float)USECS_PER_MSEC; } // in msec std::function getHUDOperator(); @@ -220,6 +223,8 @@ protected: std::function _hudOperator { std::function() }; + MovingAverage _movingAveragePresent; + private: QMutex _presentMutex; QWaitCondition _presentCondition; diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index f70daf1e77..de2d41be6b 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -91,6 +91,7 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness <@endfunc@> +<@include Haze.slh@> <@func declareEvalSkyboxGlobalColor(supportScattering)@> @@ -101,8 +102,6 @@ vec3 albedo, vec3 fresnel, float metallic, float roughness <$declareDeferredCurvature()$> <@endif@> -<@include Haze.slh@> - vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, vec3 fresnel, float metallic, float roughness <@if supportScattering@> @@ -122,7 +121,6 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu color += ambientDiffuse; color += ambientSpecular; - // Directional vec3 directionalDiffuse; vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation @@ -135,52 +133,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // Attenuate the light if haze effect selected if ((hazeParams.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) { - // Directional light attenuation is simulated by assuming the light source is at a fixed height above the - // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height - // - // The distance is computed from the height and the directional light orientation - // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees - - // Get directional light - Light light = getLight(); - vec3 lightDirection = getLightDirection(light); - - // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) - float height_95p = 2000.0; - if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { - height_95p = -log(0.05) / hazeParams.hazeKeyLightAltitudeFactor; - } - - // Note that the sine will always be positive - float sin_pitch = sqrt(1.0 - lightDirection.y * lightDirection.y); - - float distance; - const float minimumSinPitch = 0.001; - if (sin_pitch < minimumSinPitch) { - distance = height_95p / minimumSinPitch; - } else { - distance = height_95p / sin_pitch; - } - - // Position of fragment in world coordinates - vec4 worldFragPos = invViewMat * vec4(position, 0.0); - - // Integration is from the fragment towards the light source - // Note that the haze base reference affects only the haze density as function of altitude - float hazeDensityDistribution = - hazeParams.hazeKeyLightRangeFactor * - exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); - - float hazeIntegral = hazeDensityDistribution * distance; - - // Note that t is constant and equal to -log(0.05) - // float t = hazeParams.hazeAltitudeFactor * height_95p; - // hazeIntegral *= (1.0 - exp (-t)) / t; - hazeIntegral *= 0.3171178; - - float hazeAmount = 1.0 - exp(-hazeIntegral); - - color = mix(color, vec3(0.0, 0.0, 0.0), hazeAmount); + color = computeHazeColorKeyLightAttenuation(color, lightDirection, position); } return color; @@ -213,9 +166,6 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur } <@endfunc@> - - - <@func declareEvalGlobalLightingAlphaBlended()@> <$declareLightingAmbient(1, 1, 1)$> @@ -233,7 +183,6 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl color += ambientDiffuse; color += ambientSpecular / opacity; - // Directional vec3 directionalDiffuse; vec3 directionalSpecular; @@ -244,6 +193,43 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl return color; } +vec3 evalGlobalLightingAlphaBlendedWithHaze( + mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, + vec3 albedo, vec3 fresnel, float metallic, vec3 emissive, float roughness, float opacity) +{ + <$prepareGlobalLight()$> + + color += emissive * isEmissiveEnabled(); + + // Ambient + vec3 ambientDiffuse; + vec3 ambientSpecular; + evalLightingAmbient(ambientDiffuse, ambientSpecular, lightAmbient, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, obscurance); + color += ambientDiffuse; + color += ambientSpecular / opacity; + + // Directional + vec3 directionalDiffuse; + vec3 directionalSpecular; + evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, fragEyeDir, fragNormal, roughness, metallic, fresnel, albedo, shadowAttenuation); + color += directionalDiffuse; + color += directionalSpecular / opacity; + + // Haze + if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { + vec4 colorV4 = computeHazeColor( + vec4(color, 1.0), // fragment original color + position, // fragment position in eye coordinates + fragEyeVector, // fragment position in world coordinates + invViewMat[3].y, // eye height in world coordinates + lightDirection // keylight direction vector + ); + + color = colorV4.rgb; + } + + return color; +} <@endfunc@> diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index b366e6d639..0270aa58f0 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -23,6 +23,7 @@ <@include Haze.slh@> uniform sampler2D colorMap; +uniform sampler2D linearDepthMap; vec4 unpackPositionFromZeye(vec2 texcoord) { float Zeye = -texture(linearDepthMap, texcoord).x; @@ -45,104 +46,15 @@ void main(void) { discard; } - // Distance to fragment - vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); - float distance = length(eyeFragPos.xyz); - vec4 fragColor = texture(colorMap, varTexCoord0); - vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + vec4 eyeFragPos = unpackPositionFromZeye(varTexCoord0); - // Directional light component is a function of the angle from the eye, between the fragment and the sun - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - vec4 worldFragPos = getViewInverse() * eyeFragPos; - vec3 eyeFragDir = normalize(worldFragPos.xyz); + mat4 viewInverse = getViewInverse(); + vec4 worldFragPos = viewInverse * eyeFragPos; + vec4 worldEyePos = viewInverse[3]; Light light = getLight(); vec3 lightDirection = getLightDirection(light); - float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); - float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); - - vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); - - // Use the haze colour for the glare colour, if blend is not enabled - vec4 blendedHazeColor; - if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { - blendedHazeColor = mix(hazeColor, glareColor, power); - } else { - blendedHazeColor = hazeColor; - } - - vec4 potentialFragColor; - - if ((hazeParams.hazeMode & HAZE_MODE_IS_MODULATE_COLOR) == HAZE_MODE_IS_MODULATE_COLOR) { - // Compute separately for each colour - // Haze is based on both range and altitude - // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt - - // The eyepoint position is in the last column of the matrix - vec3 worldEyePos = getViewInverse()[3].xyz; - - // Note that the haze base reference affects only the haze density as function of altitude - vec3 hazeDensityDistribution = - hazeParams.colorModulationFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); - - vec3 hazeIntegral = hazeDensityDistribution * distance; - - const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyePos.y; - if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeHeightFactor * deltaHeight; - hazeIntegral *= (1.0 - exp (-t)) / t; - } - - vec3 hazeAmount = 1.0 - exp(-hazeIntegral); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); - } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { - // Haze is based only on range - float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); - } else { - // Haze is based on both range and altitude - // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt - - // The eyepoint position is in the last column of the matrix - vec3 worldEyePos = getViewInverse()[3].xyz; - - // Note that the haze base reference affects only the haze density as function of altitude - float hazeDensityDistribution = - hazeParams.hazeRangeFactor * - exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); - - float hazeIntegral = hazeDensityDistribution * distance; - - const float slopeThreshold = 0.01; - float deltaHeight = worldFragPos.y - worldEyePos.y; - if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeHeightFactor * deltaHeight; - // Protect from wild values - if (abs(t) > 0.0000001) { - hazeIntegral *= (1.0 - exp (-t)) / t; - } - } - - float hazeAmount = 1.0 - exp(-hazeIntegral); - - // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); - } - - // Mix with background at far range - const float BLEND_DISTANCE = 27000.0; - if (distance > BLEND_DISTANCE) { - outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); - } else { - outFragColor = potentialFragColor; - } + outFragColor = computeHazeColor(fragColor, eyeFragPos.xyz, worldFragPos.xyz, worldEyePos.y, lightDirection); } - diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index de7f0ac246..15e484e055 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -40,7 +40,160 @@ layout(std140) uniform hazeBuffer { HazeParams hazeParams; }; -uniform sampler2D linearDepthMap; + +// Input: +// color - fragment original color +// lightDirection - parameters of the keylight +// worldFragPos - fragment position in world coordinates +// Output: +// fragment colour after haze effect +// +// General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission +// +vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirection, vec3 worldFragPos) { + // Directional light attenuation is simulated by assuming the light source is at a fixed height above the + // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height + // + // The distance is computed from the height and the directional light orientation + // The distance is limited to height * 1,000, which gives an angle of ~0.057 degrees + + // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) + float height_95p = 2000.0; + const float log_p_005 = log(0.05); + if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { + height_95p = -log_p_005 / hazeParams.hazeKeyLightAltitudeFactor; + } + + // Note that we need the sine to be positive + float sin_pitch = abs(lightDirection.y); + + float distance; + const float minimumSinPitch = 0.001; + if (sin_pitch < minimumSinPitch) { + distance = height_95p / minimumSinPitch; + } else { + distance = height_95p / sin_pitch; + } + + // Integration is from the fragment towards the light source + // Note that the haze base reference affects only the haze density as function of altitude + float hazeDensityDistribution = + hazeParams.hazeKeyLightRangeFactor * + exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); + + float hazeIntegral = hazeDensityDistribution * distance; + + // Note that t is constant and equal to -log(0.05) + // float t = hazeParams.hazeAltitudeFactor * height_95p; + // hazeIntegral *= (1.0 - exp (-t)) / t; + hazeIntegral *= 0.3171178; + + return color * exp(-hazeIntegral); +} + +// Input: +// fragColor - fragment original color +// eyeFragPos - fragment position in eye coordinates +// worldFragPos - fragment position in world coordinates +// worldEyeHeight - eye height in world coordinates +// Output: +// fragment colour after haze effect +// +// General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission +// +vec4 computeHazeColor(vec4 fragColor, vec3 eyeFragPos, vec3 worldFragPos, float worldEyeHeight, vec3 lightDirection) { + // Distance to fragment + float distance = length(eyeFragPos); + + // Convert haze colour from uniform into a vec4 + vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + + // Directional light component is a function of the angle from the eye, between the fragment and the sun + vec3 eyeFragDir = normalize(worldFragPos); + + float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); + float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); + + vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); + + // Use the haze colour for the glare colour, if blend is not enabled + vec4 blendedHazeColor; + if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { + blendedHazeColor = mix(hazeColor, glareColor, power); + } else { + blendedHazeColor = hazeColor; + } + + vec4 potentialFragColor; + + if ((hazeParams.hazeMode & HAZE_MODE_IS_MODULATE_COLOR) == HAZE_MODE_IS_MODULATE_COLOR) { + // Compute separately for each colour + // Haze is based on both range and altitude + // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt + + // Note that the haze base reference affects only the haze density as function of altitude + vec3 hazeDensityDistribution = + hazeParams.colorModulationFactor * + exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + + vec3 hazeIntegral = hazeDensityDistribution * distance; + + const float slopeThreshold = 0.01; + float deltaHeight = worldFragPos.y - worldEyeHeight; + if (abs(deltaHeight) > slopeThreshold) { + float t = hazeParams.hazeHeightFactor * deltaHeight; + hazeIntegral *= (1.0 - exp (-t)) / t; + } + + vec3 hazeAmount = 1.0 - exp(-hazeIntegral); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); + } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { + // Haze is based only on range + float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + } else { + // Haze is based on both range and altitude + // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt + + // Note that the haze base reference affects only the haze density as function of altitude + float hazeDensityDistribution = + hazeParams.hazeRangeFactor * + exp(-hazeParams.hazeHeightFactor * (worldEyeHeight - hazeParams.hazeBaseReference)); + + float hazeIntegral = hazeDensityDistribution * distance; + + const float slopeThreshold = 0.01; + float deltaHeight = worldFragPos.y - worldEyeHeight; + if (abs(deltaHeight) > slopeThreshold) { + float t = hazeParams.hazeHeightFactor * deltaHeight; + // Protect from wild values + const float EPSILON = 0.0000001f; + if (abs(t) > EPSILON) { + hazeIntegral *= (1.0 - exp (-t)) / t; + } + } + + float hazeAmount = 1.0 - exp(-hazeIntegral); + + // Compute color after haze effect + potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + } + + // Mix with background at far range + const float BLEND_DISTANCE = 27000.0f; + vec4 outFragColor; + if (distance > BLEND_DISTANCE) { + outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); + } else { + outFragColor = potentialFragColor; + } + + return outFragColor; +} <@endif@> diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 5c9abbabed..f860c0494e 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -163,6 +163,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); + const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); + task.addJob("DrawHazeDeferred", drawHazeInputs); + // Render transparent objects forward in LightingBuffer const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying(); task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); @@ -173,9 +176,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DebugLightClusters", debugLightClustersInputs); } - const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); - task.addJob("DrawHaze", drawHazeInputs); - const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); // Add bloom @@ -335,6 +335,13 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); + // Setup haze iff curretn zone has haze + auto hazeStage = args->_scene->getStage(); + if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { + model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); + batch.setUniformBuffer(render::ShapePipeline::Slot::HAZE_MODEL, hazePointer->getHazeParametersBuffer()); + } + // From the lighting model define a global shapKey ORED with individiual keys ShapeKey::Builder keyBuilder; if (lightingModel->isWireframeEnabled()) { diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index f51201d77d..e41b7edc77 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -16,7 +16,6 @@ #include #include "LightingModel.h" - class BeginGPURangeTimer { public: using JobModel = render::Job::ModelO; diff --git a/libraries/render-utils/src/forward_model_translucent.slf b/libraries/render-utils/src/forward_model_translucent.slf index 52e8ce50e7..906393db1f 100644 --- a/libraries/render-utils/src/forward_model_translucent.slf +++ b/libraries/render-utils/src/forward_model_translucent.slf @@ -66,7 +66,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 6cf99a68ef..38f162fdc3 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -66,7 +66,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index c46b396ebc..9d5477304c 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -76,7 +76,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalGlobalLightingAlphaBlended( + _fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf index 748eea329c..b26e70f465 100644 --- a/libraries/render-utils/src/overlay3D_model_translucent.slf +++ b/libraries/render-utils/src/overlay3D_model_translucent.slf @@ -65,7 +65,7 @@ void main(void) { vec3 fragNormal; <$transformEyeToWorldDir(cam, _normal, fragNormal)$> - vec4 color = vec4(evalGlobalLightingAlphaBlended( + vec4 color = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, occlusionTex, diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index b9eb921e9d..b16b19c8b4 100644 --- a/libraries/render-utils/src/simple_transparent_textured.slf +++ b/libraries/render-utils/src/simple_transparent_textured.slf @@ -45,7 +45,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, 1.0, diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index 20c7907bbe..ad260210a7 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -57,7 +57,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, 1.0, diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 762b7712d7..4254280fa1 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -87,6 +87,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); + slotBindings.insert(gpu::Shader::Binding(std::string("hazeParametersBuffer"), Slot::BUFFER::HAZE_MODEL)); gpu::Shader::makeProgram(*program, slotBindings); @@ -107,6 +108,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); + locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeParametersBuffer"); ShapeKey key{filter._flags}; auto gpuPipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index f0749504eb..101c060306 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -238,6 +238,7 @@ public: LIGHT, LIGHT_AMBIENT_BUFFER, FADE_PARAMETERS, + HAZE_MODEL }; enum MAP { @@ -270,6 +271,7 @@ public: int lightAmbientMapUnit; int fadeMaskTextureUnit; int fadeParameterBufferUnit; + int hazeParameterBufferUnit; }; using LocationsPointer = std::shared_ptr; diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 6f2e7d8b19..b33b330fc0 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -30,9 +30,30 @@ QString TEMP_DIR_FORMAT { "%1-%2-%3" }; const QString& PathUtils::resourcesPath() { #ifdef Q_OS_MAC - static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/"; #else - static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; + static const QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/"; +#endif + return staticResourcePath; +} + +#ifdef DEV_BUILD +const QString& PathUtils::projectRootPath() { + static QString sourceFolder; + static std::once_flag once; + std::call_once(once, [&] { + QDir thisDir = QFileInfo(__FILE__).absoluteDir(); + sourceFolder = QDir::cleanPath(thisDir.absoluteFilePath("../../../")); + }); + return sourceFolder; +} +#endif + +const QString& PathUtils::qmlBasePath() { +#ifdef DEV_BUILD + static const QString staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/qml/").toString(); +#else + static const QString staticResourcePath = "qrc:///qml/"; #endif return staticResourcePath; diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index bc058e7820..2b4fe35d97 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -34,6 +34,10 @@ class PathUtils : public QObject, public Dependency { Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT) public: static const QString& resourcesPath(); + static const QString& qmlBasePath(); +#ifdef DEV_BUILD + static const QString& projectRootPath(); +#endif static QString getAppDataPath(); static QString getAppLocalDataPath(); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 16a62cea4b..905a224ef4 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -134,9 +134,6 @@ void OffscreenUi::create() { myContext->setContextProperty("OffscreenUi", this); myContext->setContextProperty("offscreenFlags", offscreenFlags = new OffscreenFlags()); myContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); - auto tabletScriptingInterface = DependencyManager::get(); - TabletProxy* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); - myContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 14d8ec8985..90b91c5ec2 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -61,8 +61,9 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { } QUrl url { properties[SOURCE_PROPERTY].toString() }; + // If the passed URL doesn't correspond to a known scheme, assume it's a local file path if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" && - url.scheme() != "atp") { + url.scheme() != "atp" && url.scheme() != "qrc") { properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString(); } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 12220a8079..7848cb6372 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -51,6 +51,8 @@ #include "types/HFWebEngineProfile.h" #include "types/SoundEffect.h" +#include "TabletScriptingInterface.h" +#include "ToolbarScriptingInterface.h" #include "Logging.h" Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") @@ -65,7 +67,10 @@ public: void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback) { withWriteLock([&] { - for (const auto& url : urls) { + for (auto url : urls) { + if (url.isRelative()) { + url = QUrl(PathUtils::qmlBasePath() + url.toString()); + } _callbacks[url].push_back(callback); } }); @@ -102,7 +107,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list } -QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {}; +QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; struct TextureSet { // The number of surfaces with this size @@ -443,6 +448,15 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); rootContext->setContextProperty("Paths", DependencyManager::get().data()); + static std::once_flag once; + std::call_once(once, [&] { + qRegisterMetaType(); + qRegisterMetaType(); + }); + rootContext->setContextProperty("Tablet", DependencyManager::get().data()); + rootContext->setContextProperty("Toolbars", DependencyManager::get().data()); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + engine->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } QQmlEngine* acquireEngine(QQuickWindow* window) { @@ -659,10 +673,11 @@ void OffscreenQmlSurface::create() { auto qmlEngine = acquireEngine(_quickWindow); _qmlContext = new QQmlContext(qmlEngine->rootContext()); - + _qmlContext->setBaseUrl(QUrl{ PathUtils::qmlBasePath() }); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlContext->setContextProperty("eventBridge", this); _qmlContext->setContextProperty("webEntity", this); + _qmlContext->setContextProperty("QmlSurface", this); // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated @@ -801,55 +816,70 @@ QQuickItem* OffscreenQmlSurface::getRootItem() { return _rootItem; } -void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { - _qmlContext->setBaseUrl(baseUrl); +QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { + // Get any whitelist functionality + QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); + // If we have whitelisted content, we must load a new context + forceNewContext |= !callbacks.empty(); + + QQmlContext* targetContext = parent ? QQmlEngine::contextForObject(parent) : _qmlContext; + if (!targetContext) { + targetContext = _qmlContext; + } + if (_rootItem && forceNewContext) { + targetContext = new QQmlContext(targetContext); + } + + for (const auto& callback : callbacks) { + callback(targetContext); + } + + return targetContext; } -void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { + loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { + QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); + }); +} + +void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback) { + loadInternal(qmlSource, createNewContext, nullptr, onQmlLoadedCallback); +} + +void OffscreenQmlSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback) { if (QThread::currentThread() != thread()) { qCWarning(uiLogging) << "Called load on a non-surface thread"; } // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); - // Get any whitelist functionality - QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); - // If we have whitelisted content, we must load a new context - createNewContext |= !callbacks.empty(); - callbacks.push_back(onQmlLoadedCallback); - - QQmlContext* targetContext = _qmlContext; - if (_rootItem && createNewContext) { - targetContext = new QQmlContext(targetContext); - } - - - // FIXME eliminate loading of relative file paths for QML QUrl finalQmlSource = qmlSource; if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { finalQmlSource = _qmlContext->resolvedUrl(qmlSource); } + auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); if (qmlComponent->isLoading()) { connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { - finishQmlLoad(qmlComponent, targetContext, callbacks); + finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback); }); return; } - finishQmlLoad(qmlComponent, targetContext, callbacks); + finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback); } -void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) { load(qmlSource, true, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) { load(qmlSource, false, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback) { return load(QUrl(qmlSourceFile), onQmlLoadedCallback); } @@ -857,7 +887,8 @@ void OffscreenQmlSurface::clearCache() { _qmlContext->engine()->clearComponentCache(); } -void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks) { + +void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); if (qmlComponent->isError()) { for (const auto& error : qmlComponent->errors()) { @@ -879,6 +910,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext return; } + if (!newObject) { + if (!_rootItem) { + qFatal("Could not load object as root item"); + return; + } + qCWarning(uiLogging) << "Unable to load QML item"; + return; + } + + QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); + if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { + // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper + // Find a way to flag older scripts using this mechanism and wanr that this is deprecated + qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + } + qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); // All quick items should be focusable @@ -889,41 +936,30 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext newItem->setFlag(QQuickItem::ItemIsFocusScope, true); } + // Make sure we will call callback for this codepath // Call this before qmlComponent->completeCreate() otherwise ghost window appears - if (newItem && _rootItem) { - for (const auto& callback : callbacks) { - callback(qmlContext, newObject); - } - } + // If we already have a root, just set a couple of flags and the ancestry + if (_rootItem) { + callback(qmlContext, newItem); - QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); - if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { - // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper - // Find a way to flag older scripts using this mechanism and wanr that this is deprecated - qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + if (!parent) { + parent = _rootItem; + } + // Allow child windows to be destroyed from JS + QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); + newObject->setParent(parent); + newItem->setParentItem(parent); } qmlComponent->completeCreate(); qmlComponent->deleteLater(); - // If we already have a root, just set a couple of flags and the ancestry - if (newItem && _rootItem) { - // Allow child windows to be destroyed from JS - QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); - newObject->setParent(_rootItem); - if (newItem) { - newItem->setParentItem(_rootItem); - } + if (_rootItem) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); return; } - if (!newItem) { - qFatal("Could not load object as root item"); - return; - } - connect(newItem, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(QVariant))); // The root item is ready. Associate it with the window. @@ -931,11 +967,16 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext _rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setSize(_quickWindow->renderTargetSize()); - // Call this callback after rootitem is set, otherwise VrMenu wont work - for (const auto& callback : callbacks) { - callback(qmlContext, newObject); + if (_rootItem->objectName() == "tabletRoot") { + _qmlContext->setContextProperty("tabletRoot", QVariant::fromValue(_rootItem)); + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", this); + QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); + _qmlContext->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); } QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); + // Call this callback after rootitem is set, otherwise VrMenu wont work + callback(qmlContext, newItem); } void OffscreenQmlSurface::updateQuick() { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 4c23c62c12..943d4f019b 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -33,12 +33,14 @@ class QQmlContext; class QQmlComponent; class QQuickWindow; class QQuickItem; +class QJSValue; // GPU resources are typically buffered for one copy being used by the renderer, // one copy in flight, and one copy being used by the receiver #define GPU_RESOURCE_BUFFER_SIZE 3 -using QmlContextCallback = std::function; +using QmlContextCallback = std::function; +using QmlContextObjectCallback = std::function; class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis { Q_OBJECT @@ -46,7 +48,7 @@ class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -59,10 +61,15 @@ public: void resize(const QSize& size, bool forceResize = false); QSize size() const; - Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + // Usable from QML code as QmlSurface.load(url, parent, function(newItem){ ... }) + Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); + + // For C++ use + Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + void clearCache(); void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } // Optional values for event handling @@ -77,7 +84,6 @@ public: bool isPaused() const; bool getCleaned() { return _isCleaned; } - void setBaseUrl(const QUrl& baseUrl); QQuickItem* getRootItem(); QQuickWindow* getWindow(); QObject* getEventHandler(); @@ -142,13 +148,13 @@ protected: private: static QOpenGLContext* getSharedContext(); - void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks); + QQmlContext* contextForUrl(const QUrl& url, QQuickItem* parent, bool forceNewContext = false); + void loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback); + void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& onQmlLoadedCallback); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); - void setupFbo(); bool allowNewFrame(uint8_t fps); void render(); void cleanup(); - QJsonObject getGLContextData(); void disconnectAudioOutputTimer(); private slots: diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 2a0ca4a2e9..9b6b031dd9 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP QSharedPointer OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) { auto surface = QSharedPointer(new OffscreenQmlSurface()); surface->create(); - surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); surface->load(rootSource); surface->resize(QSize(100, 100)); return surface; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index d91d746f09..9bdd659279 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -32,6 +32,14 @@ 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"; +static QString getUsername() { + QString username = "Unknown user"; + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + username = accountManager->getAccountInfo().getUsername(); + } + return username; +} static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav", "/sounds/Button04.wav", @@ -39,6 +47,51 @@ static Setting::Handle tabletSoundsButtonClick("TabletSounds", QStr "/sounds/Tab01.wav", "/sounds/Tab02.wav" }); +TabletButtonListModel::TabletButtonListModel() { + +} + +TabletButtonListModel::~TabletButtonListModel() { + +} + +enum ButtonDeviceRole { + ButtonProxyRole = Qt::UserRole, +}; + +QHash TabletButtonListModel::_roles{ + { ButtonProxyRole, "buttonProxy" }, +}; + +Qt::ItemFlags TabletButtonListModel::_flags{ Qt::ItemIsSelectable | Qt::ItemIsEnabled }; + +QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.row() >= rowCount() || role != ButtonProxyRole) { + return QVariant(); + } + + return QVariant::fromValue(_buttons.at(index.row()).data()); +} + +TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) { + auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); + beginResetModel(); + _buttons.push_back(tabletButtonProxy); + endResetModel(); + return tabletButtonProxy.data(); +} + +void TabletButtonListModel::removeButton(TabletButtonProxy* button) { + auto itr = std::find(_buttons.begin(), _buttons.end(), button); + if (itr == _buttons.end()) { + qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << button; + return; + } + beginResetModel(); + _buttons.erase(itr); + endResetModel(); +} + TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); } @@ -210,9 +263,9 @@ QObject* TabletScriptingInterface::getFlags() { // TabletProxy // -static const char* TABLET_SOURCE_URL = "Tablet.qml"; -static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml"; -static const char* VRMENU_SOURCE_URL = "TabletMenu.qml"; +static const char* TABLET_HOME_SOURCE_URL = "hifi/tablet/TabletHome.qml"; +static const char* WEB_VIEW_SOURCE_URL = "hifi/tablet/TabletWebView.qml"; +static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml"; class TabletRootWindow : public QmlWindowClass { virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } @@ -247,9 +300,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { auto offscreenUi = DependencyManager::get(); if (toolbarMode) { - removeButtonsFromHomeScreen(); - addButtonsToToolbar(); - // create new desktop window auto tabletRootWindow = new TabletRootWindow(); tabletRootWindow->initQml(QVariantMap()); @@ -264,11 +314,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); } else { - removeButtonsFromToolbar(); - - if (_currentPathLoaded == TABLET_SOURCE_URL) { - addButtonsToHomeScreen(); - } else { + if (_currentPathLoaded != TABLET_HOME_SOURCE_URL) { loadHomeScreen(true); } //check if running scripts window opened and save it for reopen in Tablet @@ -282,40 +328,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } -} -static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (buttonProxy == NULL){ - qCCritical(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet buttonProxy is NULL"; - return; - } - - QVariant resultVar; - bool hasResult = QMetaObject::invokeMethod(qmlTablet, "addButtonProxy", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, buttonProxy->getProperties())); - if (!hasResult) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet has no result"; - return; - } - - QObject* qmlButton = qvariant_cast(resultVar); - if (!qmlButton) { - qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject"; - return; - } - QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot())); - buttonProxy->setQmlButton(qobject_cast(qmlButton)); -} - -static QString getUsername() { - QString username = "Unknown user"; - auto accountManager = DependencyManager::get(); - if (accountManager->isLoggedIn()) { - return accountManager->getAccountInfo().getUsername(); - } else { - return "Unknown user"; - } + emit toolbarModeChanged(); } void TabletProxy::initialScreen(const QVariant& url) { @@ -363,7 +377,7 @@ void TabletProxy::onTabletShown() { static_cast(parent())->playSound(TabletScriptingInterface::TabletOpen); if (_showRunningScripts) { _showRunningScripts = false; - pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml"); + pushOntoStack("hifi/dialogs/TabletRunningScripts.qml"); } } } @@ -397,10 +411,7 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { }); if (_toolbarMode) { - // if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet. - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL))); } // force to the tablet to go to the homescreen @@ -436,7 +447,6 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true))); } } else { - removeButtonsFromHomeScreen(); _state = State::Uninitialized; emit screenChanged(QVariant("Closed"), QVariant("")); _currentPathLoaded = ""; @@ -465,7 +475,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { } if (root) { - removeButtonsFromHomeScreen(); auto offscreenUi = DependencyManager::get(); QObject* menu = offscreenUi->getRootMenu(); QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); @@ -539,7 +548,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } if (root) { - removeButtonsFromHomeScreen(); //works only in Tablet QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); _state = State::QML; if (path != _currentPathLoaded) { @@ -621,9 +629,7 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) { if (!_toolbarMode && _qmlTabletRoot) { - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); - QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL))); + QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_HOME_SOURCE_URL))); QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound"); } else if (_toolbarMode && _desktopWindow) { // close desktop window @@ -632,8 +638,8 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { } } _state = State::Home; - emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL)); - _currentPathLoaded = TABLET_SOURCE_URL; + emit screenChanged(QVariant("Home"), QVariant(TABLET_HOME_SOURCE_URL)); + _currentPathLoaded = TABLET_HOME_SOURCE_URL; } } @@ -683,17 +689,18 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS } if (root) { - removeButtonsFromHomeScreen(); if (loadOtherBase) { - QMetaObject::invokeMethod(root, "loadTabletWebBase"); + QMetaObject::invokeMethod(root, "loadTabletWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } else { - QMetaObject::invokeMethod(root, "loadWebBase"); + QMetaObject::invokeMethod(root, "loadWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); if (_toolbarMode && _desktopWindow) { QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } - QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + _state = State::Web; + emit screenChanged(QVariant("Web"), QVariant(url)); + _currentPathLoaded = QVariant(url); } else { // tablet is not initialized yet, save information and load when // the tablet root is set @@ -702,10 +709,8 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS _initialWebPathParams.first = injectedJavaScriptUrl; _initialWebPathParams.second = loadOtherBase; _initialScreen = true; + } - _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); - _currentPathLoaded = QVariant(url); } TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { @@ -715,24 +720,7 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { return result; } - auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); - _tabletButtonProxies.push_back(tabletButtonProxy); - if (!_toolbarMode && _qmlTabletRoot) { - auto tablet = getQmlTablet(); - if (tablet) { - addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data()); - } else { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - if (toolbarProxy) { - // copy properties from tablet button proxy to toolbar button proxy. - auto toolbarButtonProxy = toolbarProxy->addButton(tabletButtonProxy->getProperties()); - tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy); - } - } - return tabletButtonProxy.data(); + return _buttons.addButton(properties); } bool TabletProxy::onHomeScreen() { @@ -751,35 +739,7 @@ void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) { return; } - auto tablet = getQmlTablet(); - if (!tablet) { - qCCritical(uiLogging) << "Could not find tablet in TabletRoot.qml"; - } - - QSharedPointer buttonProxy; - { - auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); - if (iter == _tabletButtonProxies.end()) { - qCWarning(uiLogging) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; - return; - } - buttonProxy = *iter; - _tabletButtonProxies.erase(iter); - } - - if (!_toolbarMode && _qmlTabletRoot) { - buttonProxy->setQmlButton(nullptr); - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - } else if (_toolbarMode) { - auto toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - // remove button from toolbarProxy - if (toolbarProxy) { - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } - } + _buttons.removeButton(tabletButtonProxy); } void TabletProxy::emitScriptEvent(const QVariant& msg) { @@ -808,58 +768,17 @@ void TabletProxy::sendToQml(const QVariant& msg) { } } -void TabletProxy::addButtonsToHomeScreen() { - auto tablet = getQmlTablet(); - if (!tablet || _toolbarMode) { - return; - } - for (auto& buttonProxy : _tabletButtonProxies) { - addButtonProxyToQmlTablet(tablet, buttonProxy.data()); - } - auto loader = _qmlTabletRoot->findChild("loader"); - QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen())); -} OffscreenQmlSurface* TabletProxy::getTabletSurface() { return _qmlOffscreenSurface; } -void TabletProxy::removeButtonsFromHomeScreen() { - auto tablet = getQmlTablet(); - for (auto& buttonProxy : _tabletButtonProxies) { - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); - } - buttonProxy->setQmlButton(nullptr); - } -} void TabletProxy::desktopWindowClosed() { gotoHomeScreen(); } -void TabletProxy::addButtonsToToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // copy properties from tablet button proxy to toolbar button proxy. - buttonProxy->setToolbarButtonProxy(toolbarProxy->addButton(buttonProxy->getProperties())); - } - - // make the toolbar visible - toolbarProxy->writeProperty("visible", QVariant(true)); -} - -void TabletProxy::removeButtonsFromToolbar() { - Q_ASSERT(QThread::currentThread() == thread()); - ToolbarProxy* toolbarProxy = DependencyManager::get()->getSystemToolbarProxy(); - for (auto& buttonProxy : _tabletButtonProxies) { - // remove button from toolbarProxy - toolbarProxy->removeButton(buttonProxy->getUuid().toString()); - buttonProxy->setToolbarButtonProxy(nullptr); - } -} QQuickItem* TabletProxy::getQmlTablet() const { if (!_qmlTabletRoot) { @@ -929,25 +848,6 @@ TabletButtonProxy::~TabletButtonProxy() { } } -void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - if (_qmlButton) { - QObject::disconnect(_qmlButton, &QQuickItem::destroyed, this, nullptr); - } - _qmlButton = qmlButton; - if (_qmlButton) { - QObject::connect(_qmlButton, &QQuickItem::destroyed, this, [this] { _qmlButton = nullptr; }); - } -} - -void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) { - Q_ASSERT(QThread::currentThread() == thread()); - _toolbarButtonProxy = toolbarButtonProxy; - if (_toolbarButtonProxy) { - QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot())); - } -} - QVariantMap TabletButtonProxy::getProperties() { if (QThread::currentThread() != thread()) { QVariantMap result; @@ -964,20 +864,19 @@ void TabletButtonProxy::editProperties(const QVariantMap& properties) { return; } + bool changed = false; QVariantMap::const_iterator iter = properties.constBegin(); while (iter != properties.constEnd()) { const auto& key = iter.key(); const auto& value = iter.value(); if (!_properties.contains(key) || _properties[key] != value) { - _properties[iter.key()] = iter.value(); - if (_qmlButton) { - QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection, Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value())); - } + _properties[key] = value; + changed = true; } ++iter; } - if (_toolbarButtonProxy) { - QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties)); + if (changed) { + emit propertiesChanged(); } } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index ad6a7c8001..d24b3b6947 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -12,13 +12,16 @@ #include #include -#include -#include +#include +#include +#include +#include + #include -#include -#include -#include -#include +#include +#include + +#include #include #include @@ -90,6 +93,31 @@ protected: bool _toolbarMode { false }; }; +class TabletButtonListModel : public QAbstractListModel { + Q_OBJECT + +public: + TabletButtonListModel(); + ~TabletButtonListModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override { Q_UNUSED(parent); return (int)_buttons.size(); } + QHash roleNames() const override { return _roles; } + Qt::ItemFlags flags(const QModelIndex& index) const override { return _flags; } + QVariant data(const QModelIndex& index, int role) const override; + + +protected: + friend class TabletProxy; + TabletButtonProxy* addButton(const QVariant& properties); + void removeButton(TabletButtonProxy* button); + using List = std::list>; + static QHash _roles; + static Qt::ItemFlags _flags; + std::vector> _buttons; +}; + +Q_DECLARE_METATYPE(TabletButtonListModel*); + /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet @@ -99,9 +127,10 @@ protected: class TabletProxy : public QObject { Q_OBJECT Q_PROPERTY(QString name READ getName) - Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode) + Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode NOTIFY toolbarModeChanged) Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape) Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged) + Q_PROPERTY(TabletButtonListModel* buttons READ getButtons CONSTANT) public: TabletProxy(QObject* parent, const QString& name); ~TabletProxy(); @@ -204,6 +233,7 @@ public: QQuickItem* getQmlMenu() const; + TabletButtonListModel* getButtons() { return &_buttons; } signals: /**jsdoc * Signaled when this tablet receives an event from the html/js embedded in the tablet @@ -236,21 +266,19 @@ signals: */ void tabletShownChanged(); + void toolbarModeChanged(); + protected slots: - void addButtonsToHomeScreen(); void desktopWindowClosed(); void emitWebEvent(const QVariant& msg); void onTabletShown(); + protected: - void removeButtonsFromHomeScreen(); void loadHomeScreen(bool forceOntoHomeScreen); - void addButtonsToToolbar(); - void removeButtonsFromToolbar(); bool _initialScreen { false }; QVariant _currentPathLoaded { "" }; QString _name; - std::vector> _tabletButtonProxies; QQuickItem* _qmlTabletRoot { nullptr }; OffscreenQmlSurface* _qmlOffscreenSurface { nullptr }; QmlWindowClass* _desktopWindow { nullptr }; @@ -263,6 +291,8 @@ protected: std::pair _initialWebPathParams; bool _landscape { false }; bool _showRunningScripts { false }; + + TabletButtonListModel _buttons; }; Q_DECLARE_METATYPE(TabletProxy*); @@ -274,13 +304,11 @@ Q_DECLARE_METATYPE(TabletProxy*); class TabletButtonProxy : public QObject { Q_OBJECT Q_PROPERTY(QUuid uuid READ getUuid) + Q_PROPERTY(QVariantMap properties READ getProperties NOTIFY propertiesChanged) public: TabletButtonProxy(const QVariantMap& properties); ~TabletButtonProxy(); - void setQmlButton(QQuickItem* qmlButton); - void setToolbarButtonProxy(QObject* toolbarButtonProxy); - QUuid getUuid() const { return _uuid; } /**jsdoc @@ -297,9 +325,6 @@ public: */ Q_INVOKABLE void editProperties(const QVariantMap& properties); -public slots: - void clickedSlot() { emit clicked(); } - signals: /**jsdoc * Signaled when this button has been clicked on by the user. @@ -307,12 +332,11 @@ signals: * @returns {Signal} */ void clicked(); + void propertiesChanged(); protected: QUuid _uuid; int _stableOrder; - QQuickItem* _qmlButton { nullptr }; - QObject* _toolbarButtonProxy { nullptr }; QVariantMap _properties; }; diff --git a/scripts/developer/EZrecord.js b/scripts/developer/EZrecord.js index 7fdebada79..049ccbc03d 100644 --- a/scripts/developer/EZrecord.js +++ b/scripts/developer/EZrecord.js @@ -234,7 +234,7 @@ Recorder.setUp(); - // Tablet/toolbar button. + // tablet/toolbar button. button = tablet.addButton({ icon: APP_ICON_INACTIVE, activeIcon: APP_ICON_ACTIVE, diff --git a/scripts/developer/inputRecording.js b/scripts/developer/inputRecording.js index 85bda623b3..6fb8e471cd 100644 --- a/scripts/developer/inputRecording.js +++ b/scripts/developer/inputRecording.js @@ -19,7 +19,7 @@ tablet.gotoHomeScreen(); onRecordingScreen = false; } else { - tablet.loadQMLSource("InputRecorder.qml"); + tablet.loadQMLSource("hifi/tablet/InputRecorder.qml"); onRecordingScreen = true; } } diff --git a/scripts/developer/tests/qmlTest.js b/scripts/developer/tests/qmlTest.js index c891b6a1b7..0eaabac6d1 100644 --- a/scripts/developer/tests/qmlTest.js +++ b/scripts/developer/tests/qmlTest.js @@ -1,7 +1,7 @@ print("Launching web window"); qmlWindow = new OverlayWindow({ title: 'Test Qml', - source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml", + source: "qrc:///qml/OverlayWindowTest.qml", height: 240, width: 320, toolWindow: false, diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 0a3471fa81..a93177ca38 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -15,7 +15,7 @@ var TABLET_BUTTON_NAME = "AUDIO"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; -var AUDIO_QML_SOURCE = "../audio/Audio.qml"; +var AUDIO_QML_SOURCE = "hifi/audio/Audio.qml"; var MUTE_ICONS = { icon: "icons/tablet-icons/mic-mute-i.svg", diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index a4cdc5b097..cde44e78c9 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -26,8 +26,8 @@ // Relevant Variables: // -WALLET_QML_SOURCE: The path to the Wallet QML // -onWalletScreen: true/false depending on whether we're looking at the app. - var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; + var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var onWalletScreen = false; function onButtonClicked() { if (!tablet) { @@ -61,10 +61,26 @@ function fromQml(message) { switch (message.method) { case 'passphrasePopup_cancelClicked': - case 'walletSetup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoHomeScreen(); break; + case 'walletSetup_cancelClicked': + switch (message.referrer) { + case '': // User clicked "Wallet" app + case undefined: + case null: + tablet.gotoHomeScreen(); + break; + case 'purchases': + case 'marketplace cta': + case 'mainPage': + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + default: // User needs to return to an individual marketplace item URL + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + break; + } + break; case 'needsLogIn_loginClicked': openLoginWindow(); break; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 8a7e36465d..e28f877d85 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -427,7 +427,7 @@ var toolBar = (function () { }); createButton = activeButton; tablet.screenChanged.connect(function (type, url) { - if (isActive && (type !== "QML" || url !== "Edit.qml")) { + if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml")) { that.setActive(false) } }); @@ -656,7 +656,7 @@ var toolBar = (function () { selectionDisplay.triggerMapping.disable(); tablet.landscape = false; } else { - tablet.loadQMLSource("Edit.qml", true); + tablet.loadQMLSource("hifi/tablet/Edit.qml", true); UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); gridTool.setVisible(true); diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js index 7d97f13757..082528ffc5 100644 --- a/scripts/system/generalSettings.js +++ b/scripts/system/generalSettings.js @@ -18,7 +18,7 @@ var buttonName = "Settings"; var toolBar = null; var tablet = null; - var settings = "TabletGeneralPreferences.qml" + var settings = "hifi/tablet/TabletGeneralPreferences.qml" function onClicked(){ if (tablet) { tablet.loadQMLSource(settings); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 96c1916889..698dd93f29 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -243,13 +243,14 @@ }); } - function buyButtonClicked(id, name, author, price, href) { + function buyButtonClicked(id, name, author, price, href, referrer) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, itemName: name, itemPrice: price ? parseInt(price, 10) : 0, - itemHref: href + itemHref: href, + referrer: referrer })); } @@ -316,7 +317,8 @@ $(this).closest('.grid-item').find('.item-title').text(), $(this).closest('.grid-item').find('.creator').find('.value').text(), $(this).closest('.grid-item').find('.item-cost').text(), - $(this).attr('data-href')); + $(this).attr('data-href'), + "mainPage"); }); } @@ -415,12 +417,13 @@ } purchaseButton.on('click', function () { - if ('availabile' === availability) { + if ('available' === availability) { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), cost, - href); + href, + "itemPage"); } }); maybeAddPurchasesButton(); diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js index 6df0aa3a87..69092b30fc 100644 --- a/scripts/system/libraries/accountUtils.js +++ b/scripts/system/libraries/accountUtils.js @@ -10,7 +10,7 @@ openLoginWindow = function openLoginWindow() { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Menu.triggerOption("Login / Sign Up"); } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + tablet.loadQMLOnTop("dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 004375bff7..15e72c8e8a 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -23,10 +23,9 @@ var selectionDisplay = null; // for gridTool.js to ignore var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH_BASE = "qml/hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + MARKETPLACE_CHECKOUT_QML_PATH_BASE; - var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; - var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; + var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -114,7 +113,7 @@ var selectionDisplay = null; // for gridTool.js to ignore function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; onWalletScreen = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH + onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); @@ -437,7 +436,7 @@ var selectionDisplay = null; // for gridTool.js to ignore wireEventBridge(true); tablet.sendToQml({ method: 'updateWalletReferrer', - referrer: message.itemId + referrer: message.referrer === "itemPage" ? message.itemId : message.referrer }); openWallet(); break; @@ -492,7 +491,7 @@ var selectionDisplay = null; // for gridTool.js to ignore Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; case 'purchases_openGoTo': - tablet.loadQMLSource("TabletAddressDialog.qml"); + tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml"); break; case 'purchases_itemCertificateClicked': setCertificateInfo("", message.itemCertificateId); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b5551cf596..ed7059f9f3 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -40,7 +40,7 @@ var HOVER_TEXTURES = { 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}; // almost white for now -var PAL_QML_SOURCE = "../Pal.qml"; +var PAL_QML_SOURCE = "hifi/Pal.qml"; var conserveResources = true; Script.include("/~/system/libraries/controllers.js"); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 929c6e0e5c..2a0e827932 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -24,7 +24,7 @@ print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); } - var gotoQmlSource = "TabletAddressDialog.qml"; + var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml"; var buttonName = "GOTO"; var onGotoScreen = false; var shouldActivateButton = false; diff --git a/tests/shared/src/PathUtilsTests.cpp b/tests/shared/src/PathUtilsTests.cpp new file mode 100644 index 0000000000..1c445908f7 --- /dev/null +++ b/tests/shared/src/PathUtilsTests.cpp @@ -0,0 +1,27 @@ +// +// Created by Bradley Austin Davis on 2017/11/08 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "PathUtilsTests.h" + +#include + +#include + +QTEST_MAIN(PathUtilsTests) + +void PathUtilsTests::testPathUtils() { + QString result = PathUtils::qmlBasePath(); +#if DEV_BUILD + QVERIFY(result.startsWith("file:///")); +#else + QVERIFY(result.startsWith("qrc:///")); +#endif + QVERIFY(result.endsWith("/")); +} + diff --git a/tests/shared/src/PathUtilsTests.h b/tests/shared/src/PathUtilsTests.h new file mode 100644 index 0000000000..8ae410d6cb --- /dev/null +++ b/tests/shared/src/PathUtilsTests.h @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2017/11/08 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PathUtilsTests_h +#define hifi_PathUtilsTests_h + +#include + +class PathUtilsTests : public QObject { + Q_OBJECT +private slots: + void testPathUtils(); +}; + +#endif // hifi_PathUtilsTests_h diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js index e7a135ec9e..7bc65722cd 100644 --- a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -24,7 +24,7 @@ if (onSkyboxChangerScreen) { tablet.gotoHomeScreen(); } else { - tablet.loadQMLSource("../SkyboxChanger.qml"); + tablet.loadQMLSource("hifi/SkyboxChanger.qml"); } } diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index 9eb543e768..730a5808b8 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -393,7 +393,7 @@ // Relevant Variables: // -SPECTATOR_CAMERA_QML_SOURCE: The path to the SpectatorCamera QML // -onSpectatorCameraScreen: true/false depending on whether we're looking at the spectator camera app. - var SPECTATOR_CAMERA_QML_SOURCE = Script.resourcesPath() + "qml/hifi/SpectatorCamera.qml"; + var SPECTATOR_CAMERA_QML_SOURCE = "hifi/SpectatorCamera.qml"; var onSpectatorCameraScreen = false; function onTabletButtonClicked() { if (!tablet) {