mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 14:03:17 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into black
This commit is contained in:
commit
403e77b023
95 changed files with 1269 additions and 1262 deletions
|
@ -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(
|
||||
|
|
18
interface/resources/qml/OverlayWindowTest.qml
Normal file
18
interface/resources/qml/OverlayWindowTest.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -30,8 +30,8 @@ Item {
|
|||
source: "images/wallet-bg.jpg";
|
||||
}
|
||||
|
||||
Hifi.QmlCommerce {
|
||||
id: commerce;
|
||||
Connections {
|
||||
target: Commerce;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ Item {
|
|||
id: root;
|
||||
property bool justSubmitted: false;
|
||||
|
||||
Hifi.QmlCommerce {
|
||||
id: commerce;
|
||||
Connections {
|
||||
target: Commerce;
|
||||
|
||||
onSecurityImageResult: {
|
||||
securityImageChangePageSecurityImage.source = "";
|
||||
|
|
|
@ -25,8 +25,8 @@ Item {
|
|||
|
||||
id: root;
|
||||
|
||||
Hifi.QmlCommerce {
|
||||
id: commerce;
|
||||
Connections {
|
||||
target: Commerce;
|
||||
}
|
||||
|
||||
// "Unavailable"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -1,261 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import "../../styles-uit"
|
||||
import "../audio" as HifiAudio
|
||||
|
||||
Item {
|
||||
id: tablet
|
||||
objectName: "tablet"
|
||||
property int rowIndex: 6 // by default
|
||||
property int columnIndex: 1 // point to 'go to location'
|
||||
property int count: (flowMain.children.length - 1)
|
||||
|
||||
// used to look up a button by its uuid
|
||||
function findButtonIndex(uuid) {
|
||||
if (!uuid) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (var i in flowMain.children) {
|
||||
var child = flowMain.children[i];
|
||||
if (child.uuid === uuid) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function sortButtons() {
|
||||
var children = [];
|
||||
for (var i = 0; i < flowMain.children.length; i++) {
|
||||
children[i] = flowMain.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;
|
||||
}
|
||||
});
|
||||
|
||||
flowMain.children = children;
|
||||
}
|
||||
|
||||
// called by C++ code when a button should be added to the tablet
|
||||
function addButtonProxy(properties) {
|
||||
var component = Qt.createComponent("TabletButton.qml");
|
||||
var button = component.createObject(flowMain);
|
||||
|
||||
// copy all properites to button
|
||||
var keys = Object.keys(properties).forEach(function (key) {
|
||||
button[key] = properties[key];
|
||||
});
|
||||
|
||||
// pass a reference to the tabletRoot object to the button.
|
||||
if (tabletRoot) {
|
||||
button.tabletRoot = tabletRoot;
|
||||
} else {
|
||||
button.tabletRoot = parent.parent;
|
||||
}
|
||||
|
||||
sortButtons();
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
// called by C++ code when a button should be removed from the tablet
|
||||
function removeButtonProxy(properties) {
|
||||
var index = findButtonIndex(properties.uuid);
|
||||
if (index < 0) {
|
||||
console.log("Warning: Tablet.qml could not find button with uuid = " + properties.uuid);
|
||||
} else {
|
||||
flowMain.children[index].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bgTopBar
|
||||
height: 90
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 0
|
||||
left: parent.left
|
||||
leftMargin: 0
|
||||
right: parent.right
|
||||
rightMargin: 0
|
||||
}
|
||||
|
||||
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
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2b2b2b"
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#0f212e"
|
||||
}
|
||||
}
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.top: bgTopBar.bottom
|
||||
anchors.topMargin: 0
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentWidth: parent.width
|
||||
contentHeight: flowMain.childrenRect.height + flowMain.anchors.topMargin + flowMain.anchors.bottomMargin + flowMain.spacing
|
||||
clip: true
|
||||
Flow {
|
||||
id: flowMain
|
||||
spacing: 16
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 30
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 30
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 30
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentItemState(state) {
|
||||
var index = rowIndex + columnIndex;
|
||||
|
||||
if (index >= 0 && index <= count ) {
|
||||
flowMain.children[index].state = state;
|
||||
}
|
||||
}
|
||||
|
||||
function nextItem() {
|
||||
setCurrentItemState("base state");
|
||||
var nextColumnIndex = (columnIndex + 3 + 1) % 3;
|
||||
var nextIndex = rowIndex + nextColumnIndex;
|
||||
if(nextIndex <= count) {
|
||||
columnIndex = nextColumnIndex;
|
||||
};
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function previousItem() {
|
||||
setCurrentItemState("base state");
|
||||
var prevIndex = (columnIndex + 3 - 1) % 3;
|
||||
if((rowIndex + prevIndex) <= count){
|
||||
columnIndex = prevIndex;
|
||||
}
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function upItem() {
|
||||
setCurrentItemState("base state");
|
||||
rowIndex = rowIndex - 3;
|
||||
if (rowIndex < 0 ) {
|
||||
rowIndex = (count - (count % 3));
|
||||
var index = rowIndex + columnIndex;
|
||||
if(index > count) {
|
||||
rowIndex = rowIndex - 3;
|
||||
}
|
||||
}
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function downItem() {
|
||||
setCurrentItemState("base state");
|
||||
rowIndex = rowIndex + 3;
|
||||
var index = rowIndex + columnIndex;
|
||||
if (index > count ) {
|
||||
rowIndex = 0;
|
||||
}
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function selectItem() {
|
||||
flowMain.children[rowIndex + columnIndex].clicked();
|
||||
if (tabletRoot) {
|
||||
tabletRoot.playButtonClickSound();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onRightPressed: nextItem();
|
||||
Keys.onLeftPressed: previousItem();
|
||||
Keys.onDownPressed: downItem();
|
||||
Keys.onUpPressed: upItem();
|
||||
Keys.onReturnPressed: selectItem();
|
||||
}
|
|
@ -123,7 +123,6 @@ Item {
|
|||
enabled: true
|
||||
preventStealing: true
|
||||
onClicked: {
|
||||
console.log("Tablet Button Clicked!");
|
||||
if (tabletButton.inDebugMode) {
|
||||
if (tabletButton.isActive) {
|
||||
tabletButton.isActive = false;
|
||||
|
@ -133,12 +132,12 @@ Item {
|
|||
}
|
||||
tabletButton.clicked();
|
||||
if (tabletRoot) {
|
||||
tabletInterface.playSound(TabletEnums.ButtonClick);
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
tabletButton.isEntered = true;
|
||||
tabletInterface.playSound(TabletEnums.ButtonHover);
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
|
||||
if (tabletButton.isActive) {
|
||||
tabletButton.state = "hover active state";
|
||||
|
|
159
interface/resources/qml/hifi/tablet/TabletHome.qml
Normal file
159
interface/resources/qml/hifi/tablet/TabletHome.qml
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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<ResourceImageItem>("Hifi", 1, 0, "ResourceImageItem");
|
||||
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
|
||||
qmlRegisterType<WebBrowserSuggestionsEngine>("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
|
||||
|
||||
{
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->getTablet(SYSTEM_TABLET);
|
||||
}
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
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<InputConfiguration>().data());
|
||||
|
||||
surfaceContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
// Tablet inteference with Tablet.qml. Need to avoid this in QML space
|
||||
surfaceContext->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
|
||||
surfaceContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().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<LODManager>()->autoAdjustLOD(batchTime, engineRunTime, deltaTime);
|
||||
float gpuTime = getGPUContext()->getFrameTimerGPUAverage();
|
||||
float maxRenderTime = glm::max(gpuTime, glm::max(presentTime, engineRunTime));
|
||||
DependencyManager::get<LODManager>()->autoAdjustLOD(maxRenderTime, deltaTime);
|
||||
} else {
|
||||
DependencyManager::get<LODManager>()->resetLODAdjust();
|
||||
}
|
||||
|
@ -5836,9 +5873,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue<TabletProxy>, wrapperFromScriptValue<TabletProxy>);
|
||||
qScriptRegisterMetaType(scriptEngine.data(),
|
||||
wrapperToScriptValue<TabletButtonProxy>, wrapperFromScriptValue<TabletButtonProxy>);
|
||||
// Tablet inteference with Tablet.qml. Need to avoid this in QML space
|
||||
scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
// FIXME remove these deprecated names for the tablet scripting interface
|
||||
scriptEngine->registerGlobalObject("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
|
||||
auto toolbarScriptingInterface = DependencyManager::get<ToolbarScriptingInterface>().data();
|
||||
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(toolbarScriptingInterface);
|
||||
|
@ -7245,13 +7282,17 @@ void Application::updateDisplayMode() {
|
|||
}
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto desktop = offscreenUi->getDesktop();
|
||||
|
||||
// Make the switch atomic from the perspective of other threads
|
||||
{
|
||||
std::unique_lock<std::mutex> 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);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "LODManager.h"
|
||||
|
||||
|
||||
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
|
||||
Setting::Handle<float> 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<DialogsManager>()->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<DialogsManager>()->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() {
|
||||
|
|
|
@ -19,29 +19,13 @@
|
|||
#include <SimpleMovingAverage.h>
|
||||
#include <render/Args.h>
|
||||
|
||||
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
|
||||
|
|
|
@ -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<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
|
||||
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
||||
tablet->loadQMLSource("ControllerSettings.qml");
|
||||
tablet->loadQMLSource("hifi/tablet/ControllerSettings.qml");
|
||||
|
||||
if (!hmd->getShouldShowTablet()) {
|
||||
hmd->toggleShouldShowTablet();
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
#include "Wallet.h"
|
||||
#include <AccountManager.h>
|
||||
|
||||
HIFI_QML_DEF(QmlCommerce)
|
||||
|
||||
QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
||||
QmlCommerce::QmlCommerce() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult);
|
||||
|
|
|
@ -16,16 +16,14 @@
|
|||
#define hifi_QmlCommerce_h
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <OffscreenQmlDialog.h>
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
class QmlCommerce : public OffscreenQmlDialog {
|
||||
class QmlCommerce : public QObject {
|
||||
Q_OBJECT
|
||||
HIFI_QML_DECL
|
||||
|
||||
public:
|
||||
QmlCommerce(QQuickItem* parent = nullptr);
|
||||
QmlCommerce();
|
||||
|
||||
signals:
|
||||
void walletStatusResult(uint walletStatus);
|
||||
|
|
|
@ -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<typename T>
|
||||
void DialogsManager::maybeCreateDialog(QPointer<T>& 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<HMDScriptingInterface>();
|
||||
if (visible) {
|
||||
tablet->initialScreen(url);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -200,7 +200,6 @@ void Web3DOverlay::setupQmlSurface() {
|
|||
_webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().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<TabletScriptingInterface>().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");
|
||||
|
|
|
@ -679,6 +679,7 @@ 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();
|
||||
|
@ -713,6 +714,7 @@ void OpenGLDisplayPlugin::present() {
|
|||
|
||||
gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory());
|
||||
}
|
||||
_movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent));
|
||||
}
|
||||
|
||||
float OpenGLDisplayPlugin::newFramePresentRate() const {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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<OffscreenQmlSurface>(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<TabletScriptingInterface>();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -128,7 +128,7 @@ namespace model {
|
|||
Parameters() {}
|
||||
};
|
||||
|
||||
UniformBufferView _hazeParametersBuffer;
|
||||
UniformBufferView _hazeParametersBuffer { nullptr };
|
||||
};
|
||||
|
||||
using HazePointer = std::shared_ptr<Haze>;
|
||||
|
|
|
@ -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 <cstring>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
#include <QtCore/QWaitCondition>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <shared/Bilateral.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <gpu/Forward.h>
|
||||
#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<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> getHUDOperator();
|
||||
|
||||
|
@ -220,6 +223,8 @@ protected:
|
|||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> _hudOperator { std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)>() };
|
||||
|
||||
MovingAverage<float, 10> _movingAveragePresent;
|
||||
|
||||
private:
|
||||
QMutex _presentMutex;
|
||||
QWaitCondition _presentCondition;
|
||||
|
|
|
@ -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@>
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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@>
|
||||
|
||||
|
|
|
@ -172,6 +172,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<DrawBackgroundStage>("DrawBackgroundDeferred", lightingModel);
|
||||
|
||||
const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer));
|
||||
task.addJob<DrawHaze>("DrawHazeDeferred", drawHazeInputs);
|
||||
|
||||
// Render transparent objects forward in LightingBuffer
|
||||
const auto transparentsInputs = DrawDeferred::Inputs(transparents, lightingModel).asVarying();
|
||||
task.addJob<DrawDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber);
|
||||
|
@ -182,9 +185,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
|||
task.addJob<DebugLightClusters>("DebugLightClusters", debugLightClustersInputs);
|
||||
}
|
||||
|
||||
const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer));
|
||||
task.addJob<DrawHaze>("DrawHaze", drawHazeInputs);
|
||||
|
||||
const auto toneAndPostRangeTimer = task.addJob<BeginGPURangeTimer>("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing");
|
||||
|
||||
// Add bloom
|
||||
|
@ -346,6 +346,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<HazeStage>();
|
||||
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()) {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <render/RenderFetchCullSortTask.h>
|
||||
#include "LightingModel.h"
|
||||
|
||||
|
||||
class BeginGPURangeTimer {
|
||||
public:
|
||||
using JobModel = render::Job::ModelO<BeginGPURangeTimer, gpu::RangeTimerPointer>;
|
||||
|
|
|
@ -66,7 +66,7 @@ void main(void) {
|
|||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
||||
_fragColor = vec4(evalGlobalLightingAlphaBlended(
|
||||
_fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
occlusionTex,
|
||||
|
|
|
@ -66,7 +66,7 @@ void main(void) {
|
|||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
||||
_fragColor = vec4(evalGlobalLightingAlphaBlended(
|
||||
_fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
occlusionTex,
|
||||
|
|
|
@ -76,7 +76,7 @@ void main(void) {
|
|||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
||||
_fragColor = vec4(evalGlobalLightingAlphaBlended(
|
||||
_fragColor = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
occlusionTex,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -45,7 +45,7 @@ void main(void) {
|
|||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
||||
_fragColor0 = vec4(evalGlobalLightingAlphaBlended(
|
||||
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
1.0,
|
||||
|
|
|
@ -57,7 +57,7 @@ void main(void) {
|
|||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
||||
_fragColor0 = vec4(evalGlobalLightingAlphaBlended(
|
||||
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
1.0,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Locations>;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<TabletScriptingInterface>();
|
||||
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<void(QQmlContext*, QObject*)> f) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<QUrl>& 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<PathUtils>().data());
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
qRegisterMetaType<TabletProxy*>();
|
||||
qRegisterMetaType<TabletButtonProxy*>();
|
||||
});
|
||||
rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
|
||||
TabletProxy* tablet = DependencyManager::get<TabletScriptingInterface>()->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<QmlContextCallback> 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<QmlContextCallback> 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<QmlContextCallback>& 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<QObject*>();
|
||||
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<QObject*>();
|
||||
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>();
|
||||
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() {
|
||||
|
|
|
@ -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<void(QQmlContext*, QObject*)>;
|
||||
using QmlContextCallback = std::function<void(QQmlContext*)>;
|
||||
using QmlContextObjectCallback = std::function<void(QQmlContext*, QQuickItem*)>;
|
||||
|
||||
class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis<OffscreenQmlSurface> {
|
||||
Q_OBJECT
|
||||
|
@ -46,7 +48,7 @@ class OffscreenQmlSurface : public QObject, public QEnableSharedFromThis<Offscre
|
|||
public:
|
||||
static void setSharedContext(QOpenGLContext* context);
|
||||
|
||||
static QmlContextCallback DEFAULT_CONTEXT_CALLBACK;
|
||||
static QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK;
|
||||
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& 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<QmlContextCallback>& 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:
|
||||
|
|
|
@ -45,7 +45,6 @@ void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedP
|
|||
QSharedPointer<OffscreenQmlSurface> OffscreenQmlSurfaceCache::buildSurface(const QString& rootSource) {
|
||||
auto surface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface());
|
||||
surface->create();
|
||||
surface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||
surface->load(rootSource);
|
||||
surface->resize(QSize(100, 100));
|
||||
return surface;
|
||||
|
|
|
@ -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<AccountManager>();
|
||||
if (accountManager->isLoggedIn()) {
|
||||
username = accountManager->getAccountInfo().getUsername();
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
static Setting::Handle<QStringList> tabletSoundsButtonClick("TabletSounds", QStringList { "/sounds/Button06.wav",
|
||||
"/sounds/Button04.wav",
|
||||
|
@ -39,6 +47,51 @@ static Setting::Handle<QStringList> tabletSoundsButtonClick("TabletSounds", QStr
|
|||
"/sounds/Tab01.wav",
|
||||
"/sounds/Tab02.wav" });
|
||||
|
||||
TabletButtonListModel::TabletButtonListModel() {
|
||||
|
||||
}
|
||||
|
||||
TabletButtonListModel::~TabletButtonListModel() {
|
||||
|
||||
}
|
||||
|
||||
enum ButtonDeviceRole {
|
||||
ButtonProxyRole = Qt::UserRole,
|
||||
};
|
||||
|
||||
QHash<int, QByteArray> 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<TabletButtonProxy>(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>("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<OffscreenUi>();
|
||||
|
||||
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<QObject *>(resultVar);
|
||||
if (!qmlButton) {
|
||||
qCWarning(uiLogging) << "TabletScriptingInterface addButtonProxyToQmlTablet result not a QObject";
|
||||
return;
|
||||
}
|
||||
QObject::connect(qmlButton, SIGNAL(clicked()), buttonProxy, SLOT(clickedSlot()));
|
||||
buttonProxy->setQmlButton(qobject_cast<QQuickItem*>(qmlButton));
|
||||
}
|
||||
|
||||
static QString getUsername() {
|
||||
QString username = "Unknown user";
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
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<TabletScriptingInterface*>(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<QQuickItem*>("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<OffscreenUi>();
|
||||
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<QQuickItem*>("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<TabletButtonProxy>(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<TabletScriptingInterface>()->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<TabletButtonProxy> 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<TabletScriptingInterface>()->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,57 +768,16 @@ 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<QQuickItem*>("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<TabletScriptingInterface>()->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<TabletScriptingInterface>()->getSystemToolbarProxy();
|
||||
for (auto& buttonProxy : _tabletButtonProxies) {
|
||||
// remove button from toolbarProxy
|
||||
toolbarProxy->removeButton(buttonProxy->getUuid().toString());
|
||||
buttonProxy->setToolbarButtonProxy(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
QQuickItem* TabletProxy::getQmlTablet() const {
|
||||
if (!_qmlTabletRoot) {
|
||||
|
@ -928,25 +847,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;
|
||||
|
@ -963,20 +863,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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,16 @@
|
|||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QAbstractListModel>
|
||||
|
||||
#include <QtScript/QScriptValue>
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValueIterator>
|
||||
#include <QQuickItem>
|
||||
#include <QUuid>
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtScript/QScriptValueIterator>
|
||||
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
@ -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<int, QByteArray> 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<QSharedPointer<TabletButtonProxy>>;
|
||||
static QHash<int, QByteArray> _roles;
|
||||
static Qt::ItemFlags _flags;
|
||||
std::vector<QSharedPointer<TabletButtonProxy>> _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<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
|
||||
QQuickItem* _qmlTabletRoot { nullptr };
|
||||
OffscreenQmlSurface* _qmlOffscreenSurface { nullptr };
|
||||
QmlWindowClass* _desktopWindow { nullptr };
|
||||
|
@ -263,6 +291,8 @@ protected:
|
|||
std::pair<QVariant, bool> _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;
|
||||
};
|
||||
|
||||
|
|
|
@ -234,7 +234,7 @@
|
|||
|
||||
Recorder.setUp();
|
||||
|
||||
// Tablet/toolbar button.
|
||||
// tablet/toolbar button.
|
||||
button = tablet.addButton({
|
||||
icon: APP_ICON_INACTIVE,
|
||||
activeIcon: APP_ICON_ACTIVE,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
tablet.gotoHomeScreen();
|
||||
onRecordingScreen = false;
|
||||
} else {
|
||||
tablet.loadQMLSource("InputRecorder.qml");
|
||||
tablet.loadQMLSource("hifi/tablet/InputRecorder.qml");
|
||||
onRecordingScreen = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
27
tests/shared/src/PathUtilsTests.cpp
Normal file
27
tests/shared/src/PathUtilsTests.cpp
Normal file
|
@ -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 <QtTest/QtTest>
|
||||
|
||||
#include <PathUtils.h>
|
||||
|
||||
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("/"));
|
||||
}
|
||||
|
20
tests/shared/src/PathUtilsTests.h
Normal file
20
tests/shared/src/PathUtilsTests.h
Normal file
|
@ -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 <QtCore/QObject>
|
||||
|
||||
class PathUtilsTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testPathUtils();
|
||||
};
|
||||
|
||||
#endif // hifi_PathUtilsTests_h
|
|
@ -24,7 +24,7 @@
|
|||
if (onSkyboxChangerScreen) {
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
tablet.loadQMLSource("../SkyboxChanger.qml");
|
||||
tablet.loadQMLSource("hifi/SkyboxChanger.qml");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue