diff --git a/interface/resources/qml/+android_interface/Web3DSurface.qml b/interface/resources/qml/+android_interface/Web3DSurface.qml
index c4a613222e..307e4b921d 100644
--- a/interface/resources/qml/+android_interface/Web3DSurface.qml
+++ b/interface/resources/qml/+android_interface/Web3DSurface.qml
@@ -9,68 +9,94 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
Item {
+ id: root
+ anchors.fill: parent
+ property string url: ""
+ property string scriptUrl: null
+ property bool useBackground: true
+ property string userAgent: ""
- property string url
- RadialGradient {
+ onUrlChanged: {
+ load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
+ }
+
+ onScriptUrlChanged: {
+ if (loader.item) {
+ if (root.webViewLoaded) {
+ loader.item.scriptUrl = root.scriptUrl;
+ }
+ } else {
+ load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
+ }
+ }
+
+ onUseBackgroundChanged: {
+ if (loader.item) {
+ if (root.webViewLoaded) {
+ loader.item.useBackground = root.useBackground;
+ }
+ } else {
+ load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
+ }
+ }
+
+ onUserAgentChanged: {
+ if (loader.item) {
+ if (root.webViewLoaded) {
+ loader.item.userAgent = root.userAgent;
+ }
+ } else {
+ load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
+ }
+ }
+
+ // Handle message traffic from our loaded QML to the script that launched us
+ onItemChanged: {
+ if (loader.item && loader.item.sendToScript) {
+ loader.item.sendToScript.connect(sendToScript);
+ }
+ }
+
+ property var item: null
+ property bool webViewLoaded: false
+
+ // Handle message traffic from the script that launched us to the loaded QML
+ function fromScript(message) {
+ if (loader.item && loader.item.fromScript) {
+ loader.item.fromScript(message);
+ }
+ }
+
+ Loader {
+ id: loader
anchors.fill: parent
- gradient: Gradient {
- GradientStop { position: 0.0; color: "#262626" }
- GradientStop { position: 1.0; color: "#000000" }
+ }
+
+ function load(url, scriptUrl, useBackground, userAgent) {
+ // Ensure we reset any existing item to "about:blank" to ensure web audio stops: DEV-2375
+ if (loader.item && root.webViewLoaded) {
+ loader.item.url = "about:blank"
+ }
+
+ if (url.match(/\.qml$/)) {
+ root.webViewLoaded = false;
+ loader.setSource(url);
+ } else {
+ root.webViewLoaded = true;
+ loader.setSource("./Web3DSurfaceAndroid.qml", {
+ url: url,
+ scriptUrl: scriptUrl,
+ useBackground: useBackground,
+ userAgent: userAgent
+ });
}
}
- function shortUrl(url) {
- var hostBegin = url.indexOf("://");
- if (hostBegin > -1) {
- url = url.substring(hostBegin + 3);
- }
-
- var portBegin = url.indexOf(":");
- if (portBegin > -1) {
- url = url.substring(0, portBegin);
- }
-
- var pathBegin = url.indexOf("/");
- if (pathBegin > -1) {
- url = url.substring(0, pathBegin);
- }
-
- if (url.length > 45) {
- url = url.substring(0, 45);
- }
-
- return url;
+ Component.onCompleted: {
+ load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
- Text {
- id: urlText
- text: shortUrl(url)
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- anchors.fill: parent
- anchors.rightMargin: 10
- anchors.leftMargin: 10
- font.family: "Cairo"
- font.weight: Font.DemiBold
- font.pointSize: 48
- fontSizeMode: Text.Fit
- color: "#FFFFFF"
- minimumPixelSize: 5
- }
-
- Image {
- id: hand
- source: "../../icons/hand.svg"
- width: 300
- height: 300
- anchors.bottom: parent.bottom
- anchors.right: parent.right
- anchors.bottomMargin: 100
- anchors.rightMargin: 100
- }
-
-
+ signal sendToScript(var message);
}
diff --git a/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml b/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml
new file mode 100644
index 0000000000..6a9f76bc73
--- /dev/null
+++ b/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml
@@ -0,0 +1,82 @@
+//
+// Web3DSurface.qml
+//
+// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018
+// Copyright 2016 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtGraphicalEffects 1.0
+
+Item {
+
+ property string url
+ property string scriptUrl
+ property bool useBackground
+ property string userAgent
+
+ RadialGradient {
+ anchors.fill: parent
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#262626" }
+ GradientStop { position: 1.0; color: "#000000" }
+ }
+ }
+
+ function destroy() { }
+
+ function shortUrl(url) {
+ var hostBegin = url.indexOf("://");
+ if (hostBegin > -1) {
+ url = url.substring(hostBegin + 3);
+ }
+
+ var portBegin = url.indexOf(":");
+ if (portBegin > -1) {
+ url = url.substring(0, portBegin);
+ }
+
+ var pathBegin = url.indexOf("/");
+ if (pathBegin > -1) {
+ url = url.substring(0, pathBegin);
+ }
+
+ if (url.length > 45) {
+ url = url.substring(0, 45);
+ }
+
+ return url;
+ }
+
+ Text {
+ id: urlText
+ text: shortUrl(url)
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ anchors.fill: parent
+ anchors.rightMargin: 10
+ anchors.leftMargin: 10
+ font.family: "Cairo"
+ font.weight: Font.DemiBold
+ font.pointSize: 48
+ fontSizeMode: Text.Fit
+ color: "#FFFFFF"
+ minimumPixelSize: 5
+ }
+
+ Image {
+ id: hand
+ source: "../../icons/hand.svg"
+ width: 300
+ height: 300
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.bottomMargin: 100
+ anchors.rightMargin: 100
+ }
+
+
+}
diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml
index 3bee87d669..3380eabeac 100644
--- a/interface/resources/qml/Web3DSurface.qml
+++ b/interface/resources/qml/Web3DSurface.qml
@@ -10,8 +10,8 @@
//
import QtQuick 2.5
-
-import "controls" as Controls
+import controlsUit 1.0 as Controls
+import "controls"
Item {
id: root
@@ -26,45 +26,78 @@ Item {
}
onScriptUrlChanged: {
- if (root.item) {
- root.item.scriptUrl = root.scriptUrl;
+ if (loader.item) {
+ if (root.webViewLoaded) {
+ loader.item.scriptUrl = root.scriptUrl;
+ }
} else {
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
}
onUseBackgroundChanged: {
- if (root.item) {
- root.item.useBackground = root.useBackground;
+ if (loader.item) {
+ if (root.webViewLoaded) {
+ loader.item.useBackground = root.useBackground;
+ }
} else {
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
}
-
+
onUserAgentChanged: {
- if (root.item) {
- root.item.userAgent = root.userAgent;
+ if (loader.item) {
+ if (root.webViewLoaded) {
+ loader.item.userAgent = root.userAgent;
+ }
} else {
load(root.url, root.scriptUrl, root.useBackground, root.userAgent);
}
}
+ // Handle message traffic from our loaded QML to the script that launched us
+ onItemChanged: {
+ if (loader.item && loader.item.sendToScript) {
+ loader.item.sendToScript.connect(sendToScript);
+ }
+ }
+
property var item: null
+ property bool webViewLoaded: false
+
+ // Handle message traffic from the script that launched us to the loaded QML
+ function fromScript(message) {
+ if (loader.item && loader.item.fromScript) {
+ loader.item.fromScript(message);
+ }
+ }
+
+ Loader {
+ id: loader
+ anchors.fill: parent
+ }
function load(url, scriptUrl, useBackground, userAgent) {
// Ensure we reset any existing item to "about:blank" to ensure web audio stops: DEV-2375
- if (root.item != null) {
- root.item.url = "about:blank"
- root.item.destroy()
- root.item = null
+ if (loader.item && root.webViewLoaded) {
+ if (root.webViewLoaded) {
+ loader.item.url = "about:blank"
+ }
+ loader.setSource(undefined);
+ }
+
+ if (url.match(/\.qml$/)) {
+ root.webViewLoaded = false;
+ loader.setSource(url);
+ } else {
+ root.webViewLoaded = true;
+ loader.setSource("./controls/WebView.qml", {
+ url: url,
+ scriptUrl: scriptUrl,
+ useBackground: useBackground,
+ userAgent: userAgent
+ });
}
- QmlSurface.load("./controls/WebView.qml", root, function(newItem) {
- root.item = newItem
- root.item.url = url
- root.item.scriptUrl = scriptUrl
- root.item.useBackground = useBackground
- root.item.userAgent = userAgent
- })
}
Component.onCompleted: {
diff --git a/interface/resources/qml/controlsUit/ProxyWebView.qml b/interface/resources/qml/controlsUit/ProxyWebView.qml
index 2b13760962..651bb55124 100644
--- a/interface/resources/qml/controlsUit/ProxyWebView.qml
+++ b/interface/resources/qml/controlsUit/ProxyWebView.qml
@@ -13,6 +13,8 @@ Rectangle {
property string url: "";
property bool canGoBack: false
property bool canGoForward: false
+ property bool useBackground: false
+ property string userAgent: ""
property string icon: ""
property var profile: {}
diff --git a/interface/resources/serverless/Scripts/Wizard.qml b/interface/resources/serverless/Scripts/Wizard.qml
new file mode 100644
index 0000000000..a5b70df258
--- /dev/null
+++ b/interface/resources/serverless/Scripts/Wizard.qml
@@ -0,0 +1,599 @@
+//
+// Wizard.qml
+//
+// Created by keeshii on 26 Sep 2023
+// Copyright 2023 Overte, Org.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.12
+
+import stylesUit 1.0 as HifiStylesUit
+import controlsUit 1.0 as HifiControls
+import "qrc:////qml//styles" as HifiStyles
+import "qrc:////qml//hifi" as Hifi
+
+Rectangle {
+ id: wizard
+ color: "#433952"
+
+ property int performancePreset: 0
+ property int refreshRateProfile: 0
+ property string displayName: ""
+
+ property bool keyboardEnabled: false
+ property bool punctuationMode: false
+ property bool keyboardRaised: false
+
+ function setStep(stepNum) {
+ stepList.completed = stepNum
+ switch (stepNum) {
+ case 0:
+ loader.sourceComponent = step1;
+ break;
+ case 1:
+ loader.sourceComponent = step2;
+ break;
+ case 2:
+ loader.sourceComponent = step3;
+ break;
+ case 3:
+ loader.sourceComponent = step4;
+ break;
+ case 4:
+ loader.sourceComponent = step5;
+ break;
+ default:
+ loader.setSource(undefined);
+ }
+ }
+
+ function completeWizard() {
+ var completionMessage = {
+ command: "complete-wizard",
+ data: {
+ performancePreset: wizard.performancePreset,
+ refreshRateProfile: wizard.refreshRateProfile,
+ displayName: wizard.displayName
+ }
+ };
+ eventBridge.emitWebEvent(JSON.stringify(completionMessage));
+ }
+
+ function handleWebEvent(message) {
+ var messageJSON = JSON.parse(message);
+ if (messageJSON.command === "script-to-web-initialize") {
+ wizard.performancePreset = messageJSON.data.performancePreset;
+ wizard.refreshRateProfile = messageJSON.data.refreshRateProfile;
+ wizard.displayName = messageJSON.data.displayName;
+ }
+ }
+
+ function initializeWizard() {
+ var initializeCommand = {"command": "first-run-wizard-ready"};
+ eventBridge.emitWebEvent(JSON.stringify(initializeCommand));
+ }
+
+ function stop() {
+ wizard.keyboardEnabled = false;
+ }
+
+ // Layout constants constants
+ HifiStyles.HifiConstants { id: hifi }
+
+ Rectangle {
+ id: steps
+ color: "#26202e"
+ width: parent.width - 8 * hifi.layout.spacing
+ height: hifi.layout.rowHeight + 6 * hifi.layout.spacing
+ anchors.top: wizard.top
+ anchors.topMargin: 4 * hifi.layout.spacing
+ anchors.horizontalCenter: wizard.horizontalCenter
+ radius: hifi.layout.spacing
+
+ ListView {
+ id: stepList
+ anchors.fill: parent
+ orientation: ListView.Horizontal
+
+ property int completed: 0
+
+ delegate: Item {
+ id: stepItem
+ width: stepList.width / 5
+ height: stepList.height
+ property int num: index
+
+ Rectangle {
+ width: parent.width
+ height: 1
+ anchors.left: stepCircle.horizontalCenter
+ anchors.top: stepCircle.verticalCenter
+ visible: stepItem.num + 1 < stepList.model.count
+ color: stepList.completed > stepItem.num ? "#4bb543" : "gray"
+ }
+
+ Rectangle {
+ id: stepCircle
+ color: stepList.completed > stepItem.num ? "#4bb543"
+ : (stepList.completed === stepItem.num ? "white" : "gray")
+ width: hifi.layout.rowHeight
+ height: hifi.layout.rowHeight
+ radius: hifi.layout.rowHeight / 2
+ anchors.top: stepItem.top
+ anchors.topMargin: hifi.layout.spacing
+ anchors.horizontalCenter: stepItem.horizontalCenter
+
+ Text {
+ id: stepNum
+ text: String(stepItem.num + 1)
+ color: stepList.completed === stepItem.num ? hifi.colors.text : "white"
+ anchors.centerIn: stepCircle
+ }
+ }
+
+ Text {
+ id: stepName
+ text: name
+ color: "white"
+ anchors.top: stepCircle.bottom
+ anchors.topMargin: hifi.layout.spacing
+ anchors.horizontalCenter: stepItem.horizontalCenter
+ font.bold: true
+ }
+ }
+
+ model: ListModel {
+ ListElement { name: "Welcome" }
+ ListElement { name: "Quality" }
+ ListElement { name: "Performance" }
+ ListElement { name: "Display Name" }
+ ListElement { name: "Completion" }
+ }
+
+ }
+ }
+
+ Loader {
+ id: loader
+ width: parent.width - 8 * hifi.layout.spacing
+ height: parent.height - steps.height - backButton.height - 12 * hifi.layout.spacing
+ anchors.top: steps.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+ anchors.horizontalCenter: wizard.horizontalCenter
+ sourceComponent: step1
+ }
+
+ Component {
+ id: step1
+
+ Item {
+ id: step1Body
+ anchors.fill: loader
+
+ Text {
+ id: step1Header
+ width: parent.width
+ text: "Welcome to Overte!"
+ color: "white"
+ font.pixelSize: hifi.fonts.headerPixelSize
+ font.bold: true
+
+ anchors.top: step1Body.top
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ Text {
+ id: step1Text1
+ width: parent.width
+ text:
+ "Let's get you setup to experience the virtual world.
" +
+ "First, we need to select some performance and graphics quality options.
" +
+ "
" +
+ "Press Continue when you are ready."
+ color: "white"
+ wrapMode: Text.Wrap
+ textFormat: TextEdit.RichText
+
+ anchors.top: step1Header.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+ }
+ }
+
+ Component {
+ id: step2
+
+ Item {
+ id: step2Body
+ anchors.fill: loader
+
+ Text {
+ id: step2Header
+ width: parent.width
+ text: "Quality"
+ color: "white"
+ font.pixelSize: hifi.fonts.headerPixelSize
+ font.bold: true
+
+ anchors.top: step2Body.top
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ Text {
+ id: step2Text1
+ width: parent.width
+ text:
+ "What level of visual quality would you like?
" +
+ "Remember! If you do not have a powerful computer,
" +
+ "you may want to set this to low or medium at most."
+ color: "white"
+ wrapMode: Text.Wrap
+ textFormat: TextEdit.RichText
+
+ anchors.top: step2Header.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ ColumnLayout {
+ anchors.top: step2Text1.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+
+ RadioButton {
+ text:
+ "Very Low Quality\n" +
+ "Slow Laptop / Very Slow Computer"
+ onClicked: wizard.performancePreset = 1
+ checked: wizard.performancePreset === 1
+ }
+ RadioButton {
+ text:
+ "Low Quality\n" +
+ "Average Laptop / Slow Computer"
+ onClicked: wizard.performancePreset = 2
+ checked: wizard.performancePreset === 2
+ }
+ RadioButton {
+ text:
+ "Medium Quality\n" +
+ "Average Computer - Recommended"
+ onClicked: wizard.performancePreset = 3
+ checked: wizard.performancePreset === 3
+ }
+ RadioButton {
+ text:
+ "High Quality\n" +
+ "Gaming Computer"
+ onClicked: wizard.performancePreset = 4
+ checked: wizard.performancePreset === 4
+ }
+ }
+ }
+ }
+
+ Component {
+ id: step3
+
+ Item {
+ id: step3Body
+ anchors.fill: loader
+
+ Text {
+ id: step3Header
+ width: parent.width
+ text: "Performance"
+ color: "white"
+ font.pixelSize: hifi.fonts.headerPixelSize
+ font.bold: true
+
+ anchors.top: step3Body.top
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ Text {
+ id: step3Text1
+ width: parent.width
+ text:
+ "Do you want a smooth experience (high refresh rate)
" +
+ "or do you want to conserve power and resources (low refresh rate) on your computer?
" +
+ "Note: This does not apply to virtual reality headsets."
+ color: "white"
+ wrapMode: Text.Wrap
+ textFormat: TextEdit.RichText
+
+ anchors.top: step3Header.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ ColumnLayout {
+ anchors.top: step3Text1.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+
+ RadioButton {
+ text:
+ "Not Smooth (20 Hz)\n" +
+ "Conserve Power"
+ onClicked: wizard.refreshRateProfile = 1
+ checked: wizard.refreshRateProfile === 1
+ }
+ RadioButton {
+ text:
+ "Smooth (30 Hz)\n" +
+ "Use Average Resources"
+ onClicked: wizard.refreshRateProfile = 2
+ checked: wizard.refreshRateProfile === 2
+ }
+ RadioButton {
+ text:
+ "Very Smooth (60 Hz)\n" +
+ "Use Maximum Resources - Recommended"
+ onClicked: wizard.refreshRateProfile = 3
+ checked: wizard.refreshRateProfile === 3
+ }
+ }
+ }
+ }
+
+ Component {
+ id: step4
+
+ Item {
+ id: step4Body
+ anchors.fill: loader
+
+ Text {
+ id: step4Header
+ width: parent.width
+ text: "Display Name"
+ color: "white"
+ font.pixelSize: hifi.fonts.headerPixelSize
+ font.bold: true
+
+ anchors.top: step4Body.top
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ Text {
+ id: step4Text1
+ width: parent.width
+ text:
+ "What should people call you?
" +
+ "This is simply a nickname, it will be shown in place of your username (if you have one)."
+ color: "white"
+ wrapMode: Text.Wrap
+ textFormat: TextEdit.RichText
+
+ anchors.top: step4Header.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ Rectangle {
+ id: inputBar
+ width: parent.width
+ height: 40
+ color: 'white'
+ anchors.top: step4Text1.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+
+ TextField {
+ id: displayName
+ text: wizard.displayName
+ focus: true
+ width: inputBar.width - inputBar.anchors.leftMargin - inputBar.anchors.rightMargin;
+ anchors {
+ left: inputBar.left;
+ leftMargin: 8;
+ verticalCenter: inputBar.verticalCenter;
+ }
+
+ onTextChanged: wizard.displayName = text
+ placeholderText: "Enter display name"
+ verticalAlignment: TextInput.AlignBottom
+ onAccepted: {
+ if (HMD.active) {
+ wizard.keyboardEnabled = false;
+ }
+ }
+
+ font {
+ family: hifi.fonts.fontFamily
+ pixelSize: hifi.fonts.pixelSize * 0.75
+ }
+
+ color: hifi.colors.text
+ background: Item {}
+ }
+
+ MouseArea {
+ anchors.fill: parent;
+ onClicked: {
+ displayName.focus = true;
+ displayName.forceActiveFocus();
+ if (HMD.active) {
+ wizard.keyboardEnabled = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id: step5
+
+ Item {
+ id: step5Body
+ anchors.fill: loader
+
+ Text {
+ id: step5Header
+ width: parent.width
+ text: "All done!"
+ color: "white"
+ font.pixelSize: hifi.fonts.headerPixelSize
+ font.bold: true
+
+ anchors.top: step5Body.top
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+
+ Text {
+ id: step5Text1
+ width: parent.width
+ text:
+ "Now you're almost ready to go!
" +
+ "Press Complete to save your setup.
" +
+ "Then take a look at the other information kiosks after completing this wizard."
+ color: "white"
+ wrapMode: Text.Wrap
+ textFormat: TextEdit.RichText
+
+ anchors.top: step5Header.bottom
+ anchors.topMargin: 2 * hifi.layout.spacing
+ }
+ }
+ }
+
+ Button {
+ id: backButton
+ text: "< Back"
+ width: nextButton.width
+ anchors.bottom: wizard.bottom
+ anchors.left: wizard.left
+ anchors.bottomMargin: 4 * hifi.layout.spacing
+ anchors.leftMargin: 4 * hifi.layout.spacing
+ visible: stepList.completed > 0
+ onClicked: setStep(stepList.completed - 1)
+
+ contentItem: Text {
+ text: backButton.text
+ font.bold: true
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ implicitWidth: 100
+ implicitHeight: 40
+ gradient: Gradient {
+ GradientStop { position: 0 ; color: backButton.hovered ? "#0599fc" : "#0599fc" }
+ GradientStop { position: 1 ; color: backButton.hovered ? "#003670" : "#002259" }
+ }
+ border.color: "#26282a"
+ border.width: 1
+ radius: 4
+ }
+ }
+
+ Button {
+ id: nextButton
+ text: "Continue >"
+ anchors.bottom: wizard.bottom
+ anchors.right: wizard.right
+ anchors.bottomMargin: 4 * hifi.layout.spacing
+ anchors.rightMargin: 4 * hifi.layout.spacing
+ visible: stepList.completed < 4
+ onClicked: setStep(stepList.completed + 1)
+ rightPadding: 2 * hifi.layout.spacing
+ leftPadding: 2 * hifi.layout.spacing
+
+ contentItem: Text {
+ text: nextButton.text
+ font.bold: true
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ implicitWidth: 100
+ implicitHeight: 40
+ gradient: Gradient {
+ GradientStop { position: 0 ; color: nextButton.hovered ? "#0599fc" : "#0599fc" }
+ GradientStop { position: 1 ; color: nextButton.hovered ? "#003670" : "#002259" }
+ }
+ border.color: "#26282a"
+ border.width: 1
+ radius: 4
+ }
+ }
+
+ Button {
+ id: completeButton
+ text: "Complete"
+ width: nextButton.width
+ anchors.bottom: wizard.bottom
+ anchors.right: wizard.right
+ anchors.bottomMargin: 4 * hifi.layout.spacing
+ anchors.rightMargin: 4 * hifi.layout.spacing
+ visible: stepList.completed === 4
+ onClicked: completeWizard()
+
+ contentItem: Text {
+ text: completeButton.text
+ font.bold: true
+ color: "white"
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ implicitWidth: 100
+ implicitHeight: 40
+ gradient: Gradient {
+ GradientStop { position: 0 ; color: nextButton.hovered ? "#59ffc2" : "#00ff00" }
+ GradientStop { position: 1 ; color: nextButton.hovered ? "#196144" : "#003600" }
+ }
+ border.color: "#26282a"
+ border.width: 1
+ radius: 4
+ }
+ }
+
+ HifiControls.Keyboard {
+ id: keyboard
+ raised: parent.keyboardEnabled && parent.keyboardRaised
+ numeric: parent.punctuationMode
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ }
+
+ // Wait for the client-entity script to load before sending events
+ Timer {
+ id: timer
+ function setTimeout(cb, delayTime) {
+ timer.interval = delayTime;
+ timer.repeat = false;
+ timer.triggered.connect(cb);
+ timer.triggered.connect(function release () {
+ timer.triggered.disconnect(cb); // This is important
+ timer.triggered.disconnect(release); // This is important as well
+ });
+ timer.start();
+ }
+ }
+
+ Component.onCompleted: {
+ eventBridge.scriptEventReceived.connect(handleWebEvent);
+ timer.setTimeout(function(){ initializeWizard(); }, 2000);
+ }
+
+ Component.onDestruction: {
+ stop();
+ }
+
+ signal sendToScript(var message);
+
+}
diff --git a/interface/resources/serverless/Scripts/wizardLoader.js b/interface/resources/serverless/Scripts/wizardLoader.js
index 0755b63dad..3446e0be78 100644
--- a/interface/resources/serverless/Scripts/wizardLoader.js
+++ b/interface/resources/serverless/Scripts/wizardLoader.js
@@ -13,7 +13,7 @@
//
(function() {
- var CONFIG_WIZARD_URL = "https://more.overte.org/tutorial/wizard.html?v=" + Math.floor(Math.random() * 65000);
+ var CONFIG_WIZARD_URL = "qrc:///serverless/Scripts/Wizard.qml";
var loaderEntityID;
var configWizardEntityID;
@@ -54,7 +54,7 @@
"parentID": loaderEntityID,
"sourceUrl": CONFIG_WIZARD_URL,
"maxFPS": 60,
- "dpi": 19,
+ "dpi": 15,
"useBackground": true,
"grab": {
"grabbable": false
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 04fb78c496..6565d9c387 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -3226,7 +3226,7 @@ void Application::initializeUi() {
return true;
} else {
for (const auto& str : safeURLS) {
- if (!str.isEmpty() && str.endsWith(".qml") && url.toString().endsWith(".qml") &&
+ if (!str.isEmpty() && url.toString().endsWith(".qml") &&
url.toString().startsWith(str)) {
qCDebug(interfaceapp) << "Found matching url!" << url.host();
return true;
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index 6e436a0dcd..c98bfe7f63 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -66,6 +66,10 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString&
const QUrl url(urlString);
auto scheme = url.scheme();
+ if (urlString.toLower().endsWith(".qml")) {
+ return ContentType::QmlContent;
+ }
+
if (scheme == HIFI_URL_SCHEME_ABOUT || scheme == HIFI_URL_SCHEME_HTTP || scheme == HIFI_URL_SCHEME_HTTPS ||
scheme == URL_SCHEME_DATA ||
urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) {
diff --git a/scripts/system/+android_interface/androidControls.js b/scripts/system/+android_interface/androidControls.js
new file mode 100644
index 0000000000..4891fe3033
--- /dev/null
+++ b/scripts/system/+android_interface/androidControls.js
@@ -0,0 +1,149 @@
+"use strict";
+//
+// androidControls.js
+//
+// Created by keeshii on September 26th, 2023.
+// Copyright 2022-2023 Overte e.V.
+//
+// This script read touch screen events and triggers mouse events.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+// SPDX-License-Identifier: Apache-2.0
+//
+
+(function () {
+
+ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
+ var DISPATCHER_TOUCH_PROPERTIES = ["id", "position", "rotation", "dimensions", "registrationPoint"];
+
+ var TAP_DELAY = 300;
+
+ function AndroidControls() {
+ this.onTouchStartFn = null;
+ this.onTouchEndFn = null;
+ this.touchStartTime = 0;
+ }
+
+ AndroidControls.prototype.intersectsOverlay = function (intersection) {
+ if (intersection && intersection.intersects && intersection.overlayID) {
+ return true;
+ }
+ return false;
+ };
+
+ AndroidControls.prototype.intersectsEntity = function (intersection) {
+ if (intersection && intersection.intersects && intersection.entityID) {
+ return true;
+ }
+ return false;
+ };
+
+ AndroidControls.prototype.findRayIntersection = function (pickRay) {
+ // Check 3D overlays and entities. Argument is an object with origin and direction.
+ var overlayRayIntersection = Overlays.findRayIntersection(pickRay);
+ var entityRayIntersection = Entities.findRayIntersection(pickRay, true);
+ var isOverlayInters = this.intersectsOverlay(overlayRayIntersection);
+ var isEntityInters = this.intersectsEntity(entityRayIntersection);
+
+ if (isOverlayInters && (!isEntityInters || overlayRayIntersection.distance < entityRayIntersection.distance)) {
+ return {type: 'overlay', obj: overlayRayIntersection};
+ } else if (isEntityInters) {
+ return {type: 'entity', obj: entityRayIntersection};
+ }
+ return false;
+ };
+
+ AndroidControls.prototype.createEventProperties = function (entityId, info, eventType) {
+ var pointerEvent = {
+ type: eventType,
+ id: 1,
+ pos2D: {x: 0, y: 0},
+ pos3D: info.obj.intersection,
+ normal: info.obj.surfaceNormal,
+ direction: info.obj.direction,
+ button: "Primary",
+ isPrimaryButton: true,
+ isLeftButton: true,
+ isPrimaryHeld: eventType === 'Press',
+ isSecondaryHeld: false,
+ isTertiaryHeld: false,
+ keyboardModifiers: 0
+ };
+
+ var properties = Entities.getEntityProperties(entityId, DISPATCHER_TOUCH_PROPERTIES);
+ if (properties.id === entityId) {
+ pointerEvent.pos2D = info.type === "entity"
+ ? projectOntoEntityXYPlane(entityId, info.obj.intersection, properties)
+ : projectOntoOverlayXYPlane(entityId, info.obj.intersection, properties);
+ }
+
+ return pointerEvent;
+ };
+
+ AndroidControls.prototype.triggerClick = function (event) {
+ var info = this.findRayIntersection(Camera.computePickRay(event.x, event.y));
+
+ if (!info) {
+ return;
+ }
+
+ var entityId = info.type === "entity" ? info.obj.entityID : info.obj.overlayID;
+ var pressEvent = this.createEventProperties(entityId, info, 'Press');
+ var releaseEvent = this.createEventProperties(entityId, info, 'Release');
+
+ Entities.sendMousePressOnEntity(entityId, pressEvent);
+ Entities.sendClickDownOnEntity(entityId, pressEvent);
+
+ Script.setTimeout(function () {
+ Entities.sendMouseReleaseOnEntity(entityId, releaseEvent);
+ Entities.sendClickReleaseOnEntity(entityId, releaseEvent);
+ }, 75);
+ };
+
+ AndroidControls.prototype.onTouchStart = function (_event) {
+ this.touchStartTime = Date.now();
+ };
+
+ AndroidControls.prototype.onTouchEnd = function (event) {
+ var now = Date.now();
+ if (now - this.touchStartTime < TAP_DELAY) {
+ this.triggerClick(event);
+ }
+ this.touchStartTime = 0;
+ };
+
+ AndroidControls.prototype.init = function () {
+ var self = this;
+ this.onTouchStartFn = function (ev) {
+ self.onTouchStart(ev);
+ };
+ this.onTouchEndFn = function (ev) {
+ self.onTouchEnd(ev);
+ };
+
+ Controller.touchBeginEvent.connect(this.onTouchStartFn);
+ Controller.touchEndEvent.connect(this.onTouchEndFn);
+ };
+
+ AndroidControls.prototype.ending = function () {
+ if (this.onTouchStartFn) {
+ Controller.touchBeginEvent.disconnect(this.onTouchStartFn);
+ }
+ if (this.onTouchEndFn) {
+ Controller.touchEndEvent.disconnect(this.onTouchEndFn);
+ }
+ this.touchStartTime = 0;
+ this.onTouchStartFn = null;
+ this.onTouchEndFn = null;
+ };
+
+ var androidControls = new AndroidControls();
+
+ Script.scriptEnding.connect(function () {
+ androidControls.ending();
+ });
+ androidControls.init();
+
+ module.exports = androidControls;
+}());
diff --git a/scripts/system/+android_interface/clickWeb.js b/scripts/system/+android_interface/clickWeb.js
index 229b2b8b82..3d215fe982 100644
--- a/scripts/system/+android_interface/clickWeb.js
+++ b/scripts/system/+android_interface/clickWeb.js
@@ -69,12 +69,12 @@ function touchEnd(event) {
var propertiesToGet = {};
propertiesToGet[overlayID] = ['url'];
var properties = Overlays.getOverlaysProperties(propertiesToGet);
- if (properties[overlayID].url) {
+ if (properties[overlayID].url && !properties[overlayID].url.match(/\.qml$/)) {
Window.openUrl(properties[overlayID].url);
}
} else if (intersection && intersection.type == 'entity' && touchEntityID == intersection.obj.entityID) {
var properties = Entities.getEntityProperties(touchEntityID, ["sourceUrl"]);
- if (properties.sourceUrl) {
+ if (properties.sourceUrl && !properties.sourceUrl.match(/\.qml$/)) {
Window.openUrl(properties.sourceUrl);
}
}
diff --git a/scripts/system/+android_interface/modes.js b/scripts/system/+android_interface/modes.js
index f495af3bba..ff9b27a2c9 100644
--- a/scripts/system/+android_interface/modes.js
+++ b/scripts/system/+android_interface/modes.js
@@ -30,6 +30,7 @@ var radar = Script.require('./radar.js');
var uniqueColor = Script.require('./uniqueColor.js');
var displayNames = Script.require('./displayNames.js');
var clickWeb = Script.require('./clickWeb.js');
+var androidControls = Script.require('./androidControls.js');
function printd(str) {
if (logEnabled) {
@@ -99,10 +100,12 @@ function switchToMode(newMode) {
radar.startRadarMode();
displayNames.ending();
clickWeb.ending();
+ androidControls.ending();
} else if (currentMode == MODE_MY_VIEW) {
// nothing to do yet
displayNames.init();
clickWeb.init();
+ androidControls.init();
} else {
printd("Unknown view mode " + currentMode);
}
@@ -121,4 +124,4 @@ Script.scriptEnding.connect(function () {
init();
-}()); // END LOCAL_SCOPE
\ No newline at end of file
+}()); // END LOCAL_SCOPE
diff --git a/scripts/system/+android_interface/radar.js b/scripts/system/+android_interface/radar.js
index 1cbe721ad0..7e5d8c0c6d 100644
--- a/scripts/system/+android_interface/radar.js
+++ b/scripts/system/+android_interface/radar.js
@@ -1119,7 +1119,7 @@ function startRadar() {
function endRadar() {
printd("-- endRadar");
- Camera.mode = "third person";
+ Camera.mode = "first person look at";
radar = false;
Controller.setVPadEnabled(true);