diff --git a/BUILD_WIN.md b/BUILD_WIN.md
index 5836d5bfb5..90d2995e7d 100644
--- a/BUILD_WIN.md
+++ b/BUILD_WIN.md
@@ -39,7 +39,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
### Step 6. Installing OpenSSL via vcpkg
- * In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows`
+ * In the vcpkg directory, install the 64 bit OpenSSL package with the command `.\vcpkg install openssl:x64-windows`
* Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
### Step 7. Running CMake to Generate Build Files
diff --git a/README.md b/README.md
index e0bbed3105..363064964a 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR.
-In this repository you'll find the source to many of the components in our
-alpha-stage virtual world. The project embraces distributed development
-and if you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net).
+This repository contains the source to many of the components in our
+alpha-stage virtual world. The project embraces distributed development.
+If you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net).
If you find a small bug and have a fix, pull requests are welcome. If you'd
like to get paid for your work, make sure you report the bug via a job on
[Worklist.net](https://worklist.net).
@@ -32,9 +32,10 @@ Running Interface
When you launch interface, you will automatically connect to our default domain: "root.highfidelity.io".
If you don't see anything, make sure your preferences are pointing to
-root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D), if you still have no luck it's possible our servers are
-simply down; if you're experiencing a major bug, let us know by adding an issue to this repository.
-Make sure to include details about your computer and how to reproduce the bug.
+root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D). If you still have no luck,
+it's possible our servers are down. If you're experiencing a major bug, let us know by
+adding an issue to this repository. Include details about your computer and how to
+reproduce the bug in your issue.
To move around in-world, use the arrow keys (and Shift + up/down to fly up or
down) or W A S D, and E or C to fly up/down. All of the other possible options
@@ -48,7 +49,8 @@ you to run the full stack of the virtual world.
In order to set up your own virtual world, you need to set up and run your own
local "domain".
-The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models.
+The domain-server gives a number different types of assignments to the assignment-client
+for different features: audio, avatars, voxels, particles, meta-voxels and models.
Follow the instructions in the [build guide](BUILD.md) to build the various components.
@@ -56,7 +58,8 @@ From the domain-server build directory, launch a domain-server.
./domain-server
-Then, run an assignment-client. The assignment-client uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port).
+Then, run an assignment-client. The assignment-client uses localhost as its assignment-server
+and talks to it on port 40102 (the default domain-server port).
In a new Terminal window, run:
@@ -64,13 +67,20 @@ In a new Terminal window, run:
Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal window.
-This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min` and `-max` options allow you to set a range of required assignment-clients, this allows you to have flexibility in the number of assignment-clients that are running. See `--help` for more options.
+This assignment-client will grab one assignment from the domain-server. You can tell the
+assignment-client what type you want it to be with the `-t` option. You can also run an
+assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min`
+and `-max` options allow you to set a range of required assignment-clients. This allows
+you to have flexibility in the number of assignment-clients that are running.
+See `--help` for more options.
./assignment-client --min 6 --max 20
-To test things out you'll want to run the Interface client.
+To test things out, you'll need to run the Interface client.
-To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field.
+To access your local domain in Interface, open your Preferences. On OS X, this is available
+in the Interface menu. On Linux, you'll find it in the File menu. Enter "localhost" in the
+"Domain server" field.
-If everything worked you should see that you are connected to at least one server.
+If everything worked, you should see that you are connected to at least one server.
Nice work!
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 3537df033d..d5058a7f40 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,10 +1,12 @@
+import org.apache.tools.ant.taskdefs.condition.Os
+
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
//buildToolsVersion '27.0.3'
- def appVersionCode = Integer.valueOf(RELEASE_NUMBER ?: 1)
+ def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1)
def appVersionName = RELEASE_NUMBER ?: "1.0"
defaultConfig {
@@ -74,10 +76,12 @@ android {
// so our merge has to depend on the external native build
variant.externalNativeBuildTasks.each { task ->
variant.mergeResources.dependsOn(task)
- def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first()
- def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first()
- runDumpSymsTask.dependsOn(task)
- variant.assemble.dependsOn(uploadDumpSymsTask)
+ if (Os.isFamily(Os.FAMILY_UNIX)) {
+ def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first()
+ def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first()
+ runDumpSymsTask.dependsOn(task)
+ variant.assemble.dependsOn(uploadDumpSymsTask)
+ }
}
variant.mergeAssets.doLast {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e763d471cb..aa70d88c52 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -29,6 +29,7 @@
+
+
+
+
+
+
diff --git a/interface/resources/icons/tablet-icons/avatar-i.svg b/interface/resources/icons/tablet-icons/avatar-i.svg
new file mode 100644
index 0000000000..2d3f80cbf8
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/avatar-i.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/images/FavoriteIconActive.svg b/interface/resources/images/FavoriteIconActive.svg
new file mode 100644
index 0000000000..5f03217d27
--- /dev/null
+++ b/interface/resources/images/FavoriteIconActive.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/images/FavoriteIconInActive.svg b/interface/resources/images/FavoriteIconInActive.svg
new file mode 100644
index 0000000000..7cca31ac66
--- /dev/null
+++ b/interface/resources/images/FavoriteIconInActive.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/interface/resources/images/avatarapp/AvatarIsland.jpg b/interface/resources/images/avatarapp/AvatarIsland.jpg
new file mode 100644
index 0000000000..f5f649abea
Binary files /dev/null and b/interface/resources/images/avatarapp/AvatarIsland.jpg differ
diff --git a/interface/resources/images/avatarapp/BodyMart.PNG b/interface/resources/images/avatarapp/BodyMart.PNG
new file mode 100644
index 0000000000..c51ca880cb
Binary files /dev/null and b/interface/resources/images/avatarapp/BodyMart.PNG differ
diff --git a/interface/resources/images/avatarapp/guy-in-circle.svg b/interface/resources/images/avatarapp/guy-in-circle.svg
new file mode 100644
index 0000000000..78cbf5186b
--- /dev/null
+++ b/interface/resources/images/avatarapp/guy-in-circle.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/+android/Web3DOverlay.qml b/interface/resources/qml/+android/Web3DOverlay.qml
new file mode 100644
index 0000000000..d7b8306d6c
--- /dev/null
+++ b/interface/resources/qml/+android/Web3DOverlay.qml
@@ -0,0 +1,76 @@
+//
+// Web3DOverlay.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
+ RadialGradient {
+ anchors.fill: parent
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#262626" }
+ GradientStop { position: 1.0; color: "#000000" }
+ }
+ }
+
+ 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/Browser.qml b/interface/resources/qml/Browser.qml
index f0475dfebd..4474cfb2cd 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -26,6 +26,7 @@ ScrollingWindow {
y: 100
Component.onCompleted: {
+ focus = true
shown = true
addressBar.text = webview.url
}
diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml
new file mode 100644
index 0000000000..800026710d
--- /dev/null
+++ b/interface/resources/qml/InteractiveWindow.qml
@@ -0,0 +1,288 @@
+//
+// InteractiveWindow.qml
+//
+// Created by Thijs Wenker on 2018-06-25
+// Copyright 2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.3
+
+import "windows" as Windows
+import "controls"
+import "controls-uit" as Controls
+import "styles"
+import "styles-uit"
+
+Windows.Window {
+ id: root;
+ HifiConstants { id: hifi }
+ title: "InteractiveWindow";
+ resizable: true;
+ // Virtual window visibility
+ shown: false;
+ focus: true;
+ property var channel;
+ // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
+ destroyOnCloseButton: false;
+
+ signal selfDestruct();
+
+ property var flags: 0;
+
+ property var source;
+ property var dynamicContent;
+ property var nativeWindow;
+
+ // custom visibility flag for interactiveWindow to proxy virtualWindow.shown / nativeWindow.visible
+ property var interactiveWindowVisible: true;
+
+ property point interactiveWindowPosition;
+
+ property size interactiveWindowSize;
+
+ // Keyboard control properties in case needed by QML content.
+ property bool keyboardEnabled: false;
+ property bool keyboardRaised: false;
+ property bool punctuationMode: false;
+
+ property int presentationMode: 0;
+
+ property var initialized: false;
+ onSourceChanged: {
+ if (dynamicContent) {
+ dynamicContent.destroy();
+ dynamicContent = null;
+ }
+ QmlSurface.load(source, contentHolder, function(newObject) {
+ dynamicContent = newObject;
+ if (dynamicContent && dynamicContent.anchors) {
+ dynamicContent.anchors.fill = contentHolder;
+ }
+ });
+ }
+
+ function updateInteractiveWindowPositionForMode() {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ x = interactiveWindowPosition.x;
+ y = interactiveWindowPosition.y;
+ } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) {
+ // default position for native window in center of main application window
+ nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2));
+ nativeWindow.y = Math.floor(Window.y + (Window.innerHeight / 2) - (interactiveWindowSize.height / 2));
+ } else {
+ nativeWindow.x = interactiveWindowPosition.x;
+ nativeWindow.y = interactiveWindowPosition.y;
+ }
+ }
+ }
+
+ function updateInteractiveWindowSizeForMode() {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ width = interactiveWindowSize.width;
+ height = interactiveWindowSize.height;
+ } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ nativeWindow.width = interactiveWindowSize.width;
+ nativeWindow.height = interactiveWindowSize.height;
+ }
+ }
+
+ function updateContentParent() {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ contentHolder.parent = root;
+ } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ contentHolder.parent = nativeWindow.contentItem;
+ }
+ }
+
+ function setupPresentationMode() {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ if (nativeWindow) {
+ nativeWindow.setVisible(false);
+ }
+ updateContentParent();
+ updateInteractiveWindowPositionForMode();
+ shown = interactiveWindowVisible;
+ } else if (presentationMode === Desktop.PresentationMode.NATIVE) {
+ shown = false;
+ if (nativeWindow) {
+ updateContentParent();
+ updateInteractiveWindowPositionForMode();
+ nativeWindow.setVisible(interactiveWindowVisible);
+ }
+ } else if (presentationMode === modeNotSet) {
+ console.error("presentationMode should be set.");
+ }
+ }
+
+ Component.onCompleted: {
+ // Fix for parent loss on OSX:
+ parent.heightChanged.connect(updateContentParent);
+ parent.widthChanged.connect(updateContentParent);
+
+ x = interactiveWindowPosition.x;
+ y = interactiveWindowPosition.y;
+ width = interactiveWindowSize.width;
+ height = interactiveWindowSize.height;
+
+ nativeWindow = Qt.createQmlObject('
+ import QtQuick 2.3;
+ import QtQuick.Window 2.3;
+
+ Window {
+ id: root;
+ Rectangle {
+ color: hifi.colors.baseGray
+ anchors.fill: parent
+ }
+ }', root, 'InteractiveWindow.qml->nativeWindow');
+ nativeWindow.title = root.title;
+ var nativeWindowFlags = Qt.Window |
+ Qt.WindowTitleHint |
+ Qt.WindowSystemMenuHint |
+ Qt.WindowCloseButtonHint |
+ Qt.WindowMaximizeButtonHint |
+ Qt.WindowMinimizeButtonHint;
+ if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) {
+ nativeWindowFlags |= Qt.WindowStaysOnTopHint;
+ }
+ nativeWindow.flags = nativeWindowFlags;
+
+ nativeWindow.x = interactiveWindowPosition.x;
+ nativeWindow.y = interactiveWindowPosition.y;
+
+ nativeWindow.width = interactiveWindowSize.width;
+ nativeWindow.height = interactiveWindowSize.height;
+
+ nativeWindow.xChanged.connect(function() {
+ if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
+ }
+ });
+ nativeWindow.yChanged.connect(function() {
+ if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
+ }
+ });
+
+ nativeWindow.widthChanged.connect(function() {
+ if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
+ }
+ });
+ nativeWindow.heightChanged.connect(function() {
+ if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
+ }
+ });
+
+ nativeWindow.closing.connect(function(closeEvent) {
+ closeEvent.accepted = false;
+ windowClosed();
+ });
+
+ // finally set the initial window mode:
+ setupPresentationMode();
+
+ initialized = true;
+ }
+
+ Component.onDestruction: {
+ parent.heightChanged.disconnect(updateContentParent);
+ parent.widthChanged.disconnect(updateContentParent);
+ }
+
+ // Handle message traffic from the script that launched us to the loaded QML
+ function fromScript(message) {
+ if (root.dynamicContent && root.dynamicContent.fromScript) {
+ root.dynamicContent.fromScript(message);
+ }
+ }
+
+ function show() {
+ interactiveWindowVisible = true;
+ raiseWindow();
+ }
+
+ function raiseWindow() {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ raise();
+ } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ nativeWindow.raise();
+ }
+ }
+
+ // Handle message traffic from our loaded QML to the script that launched us
+ signal sendToScript(var message);
+
+ onDynamicContentChanged: {
+ if (dynamicContent && dynamicContent.sendToScript) {
+ dynamicContent.sendToScript.connect(sendToScript);
+ }
+ }
+
+ onInteractiveWindowVisibleChanged: {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ shown = interactiveWindowVisible;
+ } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ if (!nativeWindow.visible && interactiveWindowVisible) {
+ nativeWindow.showNormal();
+ } else {
+ nativeWindow.setVisible(interactiveWindowVisible);
+ }
+ }
+ }
+
+ onTitleChanged: {
+ if (nativeWindow) {
+ nativeWindow.title = title;
+ }
+ }
+
+ onXChanged: {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ interactiveWindowPosition = Qt.point(x, interactiveWindowPosition.y);
+ }
+ }
+
+ onYChanged: {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y);
+ }
+ }
+
+ onWidthChanged: {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ interactiveWindowSize = Qt.size(width, interactiveWindowSize.height);
+ }
+ }
+
+ onHeightChanged: {
+ if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ interactiveWindowSize = Qt.size(interactiveWindowSize.width, height);
+ }
+ }
+
+ onPresentationModeChanged: {
+ if (initialized) {
+ setupPresentationMode();
+ }
+ }
+
+ onWindowClosed: {
+ // set invisible on close, to make it not re-appear unintended after switching PresentationMode
+ interactiveWindowVisible = false;
+
+ if ((flags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
+ selfDestruct();
+ }
+ }
+
+ Item {
+ id: contentHolder
+ anchors.fill: parent
+ }
+}
diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
index 38e65af4ca..bf7807c85d 100644
--- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
@@ -135,6 +135,8 @@ Item {
placeholderText: qsTr("Password")
echoMode: TextInput.Password
+
+ Keys.onReturnPressed: linkAccountBody.login()
}
}
diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
index 6cbd1c4837..814778a4b1 100644
--- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
@@ -177,6 +177,8 @@ Item {
root.text = "";
root.isPassword = true;
}
+
+ Keys.onReturnPressed: linkAccountBody.login()
}
CheckBox {
diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml
index 5eb99e0ece..bb30696e4c 100644
--- a/interface/resources/qml/LoginDialog/SignUpBody.qml
+++ b/interface/resources/qml/LoginDialog/SignUpBody.qml
@@ -164,6 +164,8 @@ Item {
root.text = "";
root.isPassword = focus
}
+
+ Keys.onReturnPressed: signupBody.signup()
}
Row {
diff --git a/interface/resources/qml/OverlayWindowTest.qml b/interface/resources/qml/OverlayWindowTest.qml
index 7b82b2f705..9c1b993ba8 100644
--- a/interface/resources/qml/OverlayWindowTest.qml
+++ b/interface/resources/qml/OverlayWindowTest.qml
@@ -12,7 +12,7 @@ Rectangle {
}
Label {
- text: OverlayWindowTestString
+ text: "OverlayWindowTestString"
anchors.centerIn: parent
}
}
diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml
index 2406fa048d..bff13cea54 100644
--- a/interface/resources/qml/Stats.qml
+++ b/interface/resources/qml/Stats.qml
@@ -263,6 +263,12 @@ Item {
}
StatText {
text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms"
+ }
+ StatText {
+ text: "GPU (Per pixel): " + root.gpuFrameTimePerPixel.toFixed(5) + " ns/pp"
+ }
+ StatText {
+ text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y
}
StatText {
text: "Triangles: " + root.triangles +
diff --git a/interface/resources/qml/controls-uit/+android/Button.qml b/interface/resources/qml/controls-uit/+android/Button.qml
deleted file mode 100644
index 2f05b35685..0000000000
--- a/interface/resources/qml/controls-uit/+android/Button.qml
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-// Button.qml
-//
-// Created by David Rowe on 16 Feb 2016
-// Copyright 2016 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-import QtQuick 2.5
-import QtQuick.Controls 1.4 as Original
-import QtQuick.Controls.Styles 1.4
-import TabletScriptingInterface 1.0
-
-import "../styles-uit"
-
-Original.Button {
- id: root;
-
- property int color: 0
- property int colorScheme: hifi.colorSchemes.light
- property string buttonGlyph: "";
-
- width: hifi.dimensions.buttonWidth
- height: hifi.dimensions.controlLineHeight
-
- HifiConstants { id: hifi }
-
- onHoveredChanged: {
- if (hovered) {
- Tablet.playSound(TabletEnums.ButtonHover);
- }
- }
-
- onFocusChanged: {
- if (focus) {
- Tablet.playSound(TabletEnums.ButtonHover);
- }
- }
-
- onClicked: {
- Tablet.playSound(TabletEnums.ButtonClick);
- }
-
- style: ButtonStyle {
-
- background: Rectangle {
- radius: hifi.buttons.radius
-
- border.width: (control.color === hifi.buttons.none ||
- (control.color === hifi.buttons.noneBorderless && control.hovered) ||
- (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) ||
- (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0;
- border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight :
- (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white);
-
- gradient: Gradient {
- GradientStop {
- position: 0.2
- color: {
- if (!control.enabled) {
- hifi.buttons.disabledColorStart[control.colorScheme]
- } else if (control.pressed) {
- hifi.buttons.pressedColor[control.color]
- } else if (control.hovered) {
- hifi.buttons.hoveredColor[control.color]
- } else if (!control.hovered && control.focus) {
- hifi.buttons.focusedColor[control.color]
- } else {
- hifi.buttons.colorStart[control.color]
- }
- }
- }
- GradientStop {
- position: 1.0
- color: {
- if (!control.enabled) {
- hifi.buttons.disabledColorFinish[control.colorScheme]
- } else if (control.pressed) {
- hifi.buttons.pressedColor[control.color]
- } else if (control.hovered) {
- hifi.buttons.hoveredColor[control.color]
- } else if (!control.hovered && control.focus) {
- hifi.buttons.focusedColor[control.color]
- } else {
- hifi.buttons.colorFinish[control.color]
- }
- }
- }
- }
- }
-
- label: Item {
- HiFiGlyphs {
- id: buttonGlyph;
- visible: root.buttonGlyph !== "";
- text: root.buttonGlyph === "" ? hifi.glyphs.question : root.buttonGlyph;
- // Size
- size: 34;
- // Anchors
- anchors.right: buttonText.left;
- anchors.top: parent.top;
- anchors.bottom: parent.bottom;
- // Style
- color: enabled ? hifi.buttons.textColor[control.color]
- : hifi.buttons.disabledTextColor[control.colorScheme];
- // Alignment
- horizontalAlignment: Text.AlignHCenter;
- verticalAlignment: Text.AlignVCenter;
- }
- RalewayBold {
- id: buttonText;
- anchors.centerIn: parent;
- font.capitalization: Font.AllUppercase
- color: enabled ? hifi.buttons.textColor[control.color]
- : hifi.buttons.disabledTextColor[control.colorScheme]
- size: hifi.fontSizes.buttonLabel
- verticalAlignment: Text.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- text: control.text
- }
- }
- }
-}
diff --git a/interface/resources/qml/controls-uit/+android/Table.qml b/interface/resources/qml/controls-uit/+android/Table.qml
deleted file mode 100644
index 3c1d0fcd3c..0000000000
--- a/interface/resources/qml/controls-uit/+android/Table.qml
+++ /dev/null
@@ -1,165 +0,0 @@
-//
-// Table.qml
-//
-// Created by David Rowe on 18 Feb 2016
-// Copyright 2016 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-import QtQuick.Controls 2.2 as QQC2
-
-import "../styles-uit"
-
-TableView {
- id: tableView
-
- property int colorScheme: hifi.colorSchemes.light
- readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
- property bool expandSelectedRow: false
- property bool centerHeaderText: false
- readonly property real headerSpacing: 3 //spacing between sort indicator and table header title
- property var titlePaintedPos: [] // storing extra data position behind painted
- // title text and sort indicatorin table's header
- signal titlePaintedPosSignal(int column) //signal that extradata position gets changed
-
- model: ListModel { }
-
- Component.onCompleted: {
- if (flickableItem !== null && flickableItem !== undefined) {
- tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar
- }
- }
-
- QQC2.ScrollBar {
- id: scrollbar
- parent: tableView.flickableItem
- policy: QQC2.ScrollBar.AsNeeded
- orientation: Qt.Vertical
- visible: size < 1.0
- topPadding: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1
- anchors.top: tableView.top
- anchors.left: tableView.right
- anchors.bottom: tableView.bottom
-
- background: Item {
- implicitWidth: hifi.dimensions.scrollbarBackgroundWidth
- Rectangle {
- anchors {
- fill: parent;
- topMargin: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight : 0
- }
- color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight
- : hifi.colors.tableScrollBackgroundDark
- }
- }
-
- contentItem: Item {
- implicitWidth: hifi.dimensions.scrollbarHandleWidth
- Rectangle {
- anchors.fill: parent
- radius: (width - 4)/2
- color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark
- }
- }
- }
-
- headerVisible: false
- headerDelegate: Rectangle {
- height: hifi.dimensions.tableHeaderHeight
- color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
-
-
- RalewayRegular {
- id: titleText
- x: centerHeaderText ? (parent.width - paintedWidth -
- ((sortIndicatorVisible &&
- sortIndicatorColumn === styleData.column) ?
- (titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 :
- hifi.dimensions.tablePadding
- text: styleData.value
- size: hifi.fontSizes.tableHeading
- font.capitalization: Font.AllUppercase
- color: hifi.colors.baseGrayHighlight
- horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft)
- anchors.verticalCenter: parent.verticalCenter
- }
-
- //actual image of sort indicator in glyph font only 20% of real font size
- //i.e. if the charachter size set to 60 pixels, actual image is 12 pixels
- HiFiGlyphs {
- id: titleSort
- text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
- color: hifi.colors.darkGray
- opacity: 0.6;
- size: hifi.fontSizes.tableHeadingIcon
- anchors.verticalCenter: titleText.verticalCenter
- anchors.left: titleText.right
- anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing
- visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column
- onXChanged: {
- titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth +
- paintedWidth / 5 + tableView.headerSpacing*2
- titlePaintedPosSignal(styleData.column)
- }
- }
-
- Rectangle {
- width: 1
- anchors {
- left: parent.left
- top: parent.top
- topMargin: 1
- bottom: parent.bottom
- bottomMargin: 2
- }
- color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
- visible: styleData.column > 0
- }
-
- Rectangle {
- height: 1
- anchors {
- left: parent.left
- right: parent.right
- bottom: parent.bottom
- }
- color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
- }
- }
-
- // Use rectangle to draw border with rounded corners.
- frameVisible: false
- Rectangle {
- color: "#00000000"
- anchors { fill: parent; margins: -2 }
- border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
- border.width: 2
- }
- anchors.margins: 2 // Shrink TableView to lie within border.
-
- backgroundVisible: true
-
- horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
- verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
-
- style: TableViewStyle {
- // Needed in order for rows to keep displaying rows after end of table entries.
- backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
- alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
- padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0
- }
-
- rowDelegate: Rectangle {
- height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight
- color: styleData.selected
- ? hifi.colors.primaryHighlight
- : tableView.isLightColorScheme
- ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd)
- : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
- }
-}
diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml
index 8d0f513021..1509abdae3 100644
--- a/interface/resources/qml/controls-uit/Button.qml
+++ b/interface/resources/qml/controls-uit/Button.qml
@@ -19,6 +19,8 @@ Original.Button {
property int color: 0
property int colorScheme: hifi.colorSchemes.light
+ property int fontSize: hifi.fontSizes.buttonLabel
+ property alias implicitTextWidth: buttonText.implicitWidth
property string buttonGlyph: "";
width: hifi.dimensions.buttonWidth
@@ -108,7 +110,7 @@ Original.Button {
font.capitalization: Font.AllUppercase
color: enabled ? hifi.buttons.textColor[control.color]
: hifi.buttons.disabledTextColor[control.colorScheme]
- size: hifi.fontSizes.buttonLabel
+ size: control.fontSize
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control.text
diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml
index ebfe1ff9a9..56324c55d7 100644
--- a/interface/resources/qml/controls-uit/RadioButton.qml
+++ b/interface/resources/qml/controls-uit/RadioButton.qml
@@ -25,10 +25,16 @@ Original.RadioButton {
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
- readonly property int boxSize: 14
- readonly property int boxRadius: 3
- readonly property int checkSize: 10
- readonly property int checkRadius: 2
+ property real letterSpacing: 1
+ property int fontSize: hifi.fontSizes.inputLabel
+ property int boxSize: defaultBoxSize
+ property real scaleFactor: boxSize / defaultBoxSize
+
+ readonly property int defaultBoxSize: 14
+ readonly property int boxRadius: 3 * scaleFactor
+ readonly property int checkSize: 10 * scaleFactor
+ readonly property int checkRadius: 2 * scaleFactor
+ readonly property int indicatorRadius: 7 * scaleFactor
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
@@ -44,7 +50,7 @@ Original.RadioButton {
id: box
width: boxSize
height: boxSize
- radius: 7
+ radius: indicatorRadius
x: radioButton.leftPadding
y: parent.height / 2 - height / 2
gradient: Gradient {
@@ -66,7 +72,7 @@ Original.RadioButton {
id: check
width: checkSize
height: checkSize
- radius: 7
+ radius: indicatorRadius
anchors.centerIn: parent
color: "#00B4EF"
border.width: 1
@@ -77,7 +83,8 @@ Original.RadioButton {
contentItem: RalewaySemiBold {
text: radioButton.text
- size: hifi.fontSizes.inputLabel
+ size: radioButton.fontSize
+ font.letterSpacing: letterSpacing
color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml
index 52d5a2eb99..5a4ba70080 100644
--- a/interface/resources/qml/controls-uit/SpinBox.qml
+++ b/interface/resources/qml/controls-uit/SpinBox.qml
@@ -27,6 +27,9 @@ SpinBox {
property string suffix: ""
property string labelInside: ""
property color colorLabelInside: hifi.colors.white
+ property color backgroundColor: isLightColorScheme
+ ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray)
+ : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0)
property int decimals: 2;
property real factor: Math.pow(10, decimals)
@@ -55,10 +58,14 @@ SpinBox {
onValueModified: realValue = value/factor
onValueChanged: realValue = value/factor
+ onRealValueChanged: {
+ var newValue = Math.round(realValue*factor);
+ if(value != newValue) {
+ value = newValue;
+ }
+ }
stepSize: realStepSize*factor
- value: Math.round(realValue*factor)
-
to : realTo*factor
from : realFrom*factor
@@ -69,9 +76,7 @@ SpinBox {
y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0
background: Rectangle {
- color: isLightColorScheme
- ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray)
- : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow)
+ color: backgroundColor
border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight
border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0
}
diff --git a/interface/resources/qml/controls/+android/WebEntityView.qml b/interface/resources/qml/controls/+android/WebEntityView.qml
new file mode 100644
index 0000000000..848077cea0
--- /dev/null
+++ b/interface/resources/qml/controls/+android/WebEntityView.qml
@@ -0,0 +1,76 @@
+//
+// Web3DOverlay.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
+ RadialGradient {
+ anchors.fill: parent
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "#262626" }
+ GradientStop { position: 1.0; color: "#000000" }
+ }
+ }
+
+ 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/desktop/+android/Desktop.qml b/interface/resources/qml/desktop/+android/Desktop.qml
deleted file mode 100644
index 6a68f63d0a..0000000000
--- a/interface/resources/qml/desktop/+android/Desktop.qml
+++ /dev/null
@@ -1,575 +0,0 @@
-//
-// Desktop.qml
-//
-// Created by Bradley Austin Davis on 15 Apr 2015
-// Copyright 2015 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.7
-import QtQuick.Controls 1.4
-
-import "../dialogs"
-import "../js/Utils.js" as Utils
-
-// This is our primary 'desktop' object to which all VR dialogs and windows are childed.
-FocusScope {
- id: desktop
- objectName: "desktop"
- anchors.fill: parent
-
- readonly property int invalid_position: -9999;
- property rect recommendedRect: Qt.rect(0,0,0,0);
- property var expectedChildren;
- property bool repositionLocked: true
- property bool hmdHandMouseActive: false
-
- onRepositionLockedChanged: {
- if (!repositionLocked) {
- d.handleSizeChanged();
- }
-
- }
-
- onHeightChanged: d.handleSizeChanged();
-
- onWidthChanged: d.handleSizeChanged();
-
- // Controls and windows can trigger this signal to ensure the desktop becomes visible
- // when they're opened.
- signal showDesktop();
-
- // This is for JS/QML communication, which is unused in the Desktop,
- // but not having this here results in spurious warnings about a
- // missing signal
- signal sendToScript(var message);
-
- // Allows QML/JS to find the desktop through the parent chain
- property bool desktopRoot: true
-
- // The VR version of the primary menu
- property var rootMenu: Menu {
- id: rootMenuId
- objectName: "rootMenu"
-
- property var exclusionGroups: ({});
- property Component exclusiveGroupMaker: Component {
- ExclusiveGroup {
- }
- }
-
- function addExclusionGroup(qmlAction, exclusionGroup) {
-
- var exclusionGroupId = exclusionGroup.toString();
- if(!exclusionGroups[exclusionGroupId]) {
- exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId);
- }
-
- qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId]
- }
- }
-
- // FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD
- // because shaders are 4.2, and do not include #version declarations.
- property bool gradientsSupported: Qt.platform.os != "osx" && !~GL.vendor.indexOf("ATI")
-
- readonly property alias zLevels: zLevels
- QtObject {
- id: zLevels;
- readonly property real normal: 1 // make windows always appear higher than QML overlays and other non-window controls.
- readonly property real top: 2000
- readonly property real modal: 4000
- readonly property real menu: 8000
- }
-
- QtObject {
- id: d
-
- function handleSizeChanged() {
- if (desktop.repositionLocked) {
- return;
- }
- var oldRecommendedRect = recommendedRect;
- var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect();
- var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y,
- newRecommendedRectJS.width,
- newRecommendedRectJS.height);
-
- var oldChildren = expectedChildren;
- var newChildren = d.getRepositionChildren();
- if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1)
- && (oldRecommendedRect != newRecommendedRect
- || oldChildren != newChildren)
- ) {
- expectedChildren = newChildren;
- d.repositionAll();
- }
- recommendedRect = newRecommendedRect;
- }
-
- function findChild(item, name) {
- for (var i = 0; i < item.children.length; ++i) {
- if (item.children[i].objectName === name) {
- return item.children[i];
- }
- }
- return null;
- }
-
- function findParentMatching(item, predicate) {
- while (item) {
- if (predicate(item)) {
- break;
- }
- item = item.parent;
- }
- return item;
- }
-
- function findMatchingChildren(item, predicate) {
- var results = [];
- for (var i in item.children) {
- var child = item.children[i];
- if (predicate(child)) {
- results.push(child);
- }
- }
- return results;
- }
-
- function isTopLevelWindow(item) {
- return item.topLevelWindow;
- }
-
- function isAlwaysOnTopWindow(window) {
- return window.alwaysOnTop;
- }
-
- function isModalWindow(window) {
- return window.modality !== Qt.NonModal;
- }
-
- function getTopLevelWindows(predicate) {
- return findMatchingChildren(desktop, function(child) {
- return (isTopLevelWindow(child) && (!predicate || predicate(child)));
- });
- }
-
- function getDesktopWindow(item) {
- return findParentMatching(item, isTopLevelWindow)
- }
-
- function fixupZOrder(windows, basis, topWindow) {
- windows.sort(function(a, b){ return a.z - b.z; });
-
- if ((topWindow.z >= basis) && (windows[windows.length - 1] === topWindow)) {
- return;
- }
-
- var lastZ = -1;
- var lastTargetZ = basis - 1;
- for (var i = 0; i < windows.length; ++i) {
- var window = windows[i];
- if (!window.visible) {
- continue
- }
-
- if (topWindow && (topWindow === window)) {
- continue
- }
-
- if (window.z > lastZ) {
- lastZ = window.z;
- ++lastTargetZ;
- }
- if (DebugQML) {
- console.log("Assigning z order " + lastTargetZ + " to " + window)
- }
-
- window.z = lastTargetZ;
- }
- if (topWindow) {
- ++lastTargetZ;
- if (DebugQML) {
- console.log("Assigning z order " + lastTargetZ + " to " + topWindow)
- }
- topWindow.z = lastTargetZ;
- }
-
- return lastTargetZ;
- }
-
- function raiseWindow(targetWindow) {
- var predicate;
- var zBasis;
- if (isModalWindow(targetWindow)) {
- predicate = isModalWindow;
- zBasis = zLevels.modal
- } else if (isAlwaysOnTopWindow(targetWindow)) {
- predicate = function(window) {
- return (isAlwaysOnTopWindow(window) && !isModalWindow(window));
- }
- zBasis = zLevels.top
- } else {
- predicate = function(window) {
- return (!isAlwaysOnTopWindow(window) && !isModalWindow(window));
- }
- zBasis = zLevels.normal
- }
-
- var windows = getTopLevelWindows(predicate);
- fixupZOrder(windows, zBasis, targetWindow);
- }
-
- Component.onCompleted: {
- //offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged);
- focusHack.start();
- }
-
- function onWindowFocusChanged() {
- //console.log("Focus item is " + offscreenWindow.activeFocusItem);
-
- // FIXME this needs more testing before it can go into production
- // and I already cant produce any way to have a modal dialog lose focus
- // to a non-modal one.
- /*
- var focusedWindow = getDesktopWindow(offscreenWindow.activeFocusItem);
-
- if (isModalWindow(focusedWindow)) {
- return;
- }
-
- // new focused window is not modal... check if there are any modal windows
- var windows = getTopLevelWindows(isModalWindow);
- if (0 === windows.length) {
- return;
- }
-
- // There are modal windows present, force focus back to the top-most modal window
- windows.sort(function(a, b){ return a.z - b.z; });
- windows[windows.length - 1].focus = true;
- */
-
-// var focusedItem = offscreenWindow.activeFocusItem ;
-// if (DebugQML && focusedItem) {
-// var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height);
-// focusDebugger.x = rect.x;
-// focusDebugger.y = rect.y;
-// focusDebugger.width = rect.width
-// focusDebugger.height = rect.height
-// }
- }
-
- function getRepositionChildren(predicate) {
- return findMatchingChildren(desktop, function(child) {
- return (child.shouldReposition === true && (!predicate || predicate(child)));
- });
- }
-
- function repositionAll() {
- if (desktop.repositionLocked) {
- return;
- }
-
- var oldRecommendedRect = recommendedRect;
- var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
- var newRecommendedRect = Controller.getRecommendedHUDRect();
- var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
- var windows = d.getTopLevelWindows();
- for (var i = 0; i < windows.length; ++i) {
- var targetWindow = windows[i];
- if (targetWindow.visible) {
- repositionWindow(targetWindow, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
- }
- }
-
- // also reposition the other children that aren't top level windows but want to be repositioned
- var otherChildren = d.getRepositionChildren();
- for (var i = 0; i < otherChildren.length; ++i) {
- var child = otherChildren[i];
- repositionWindow(child, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
- }
-
- }
- }
-
- property bool pinned: false
- property var hiddenChildren: []
-
- function togglePinned() {
- pinned = !pinned
- }
-
- function isPointOnWindow(point) {
- for (var i = 0; i < desktop.visibleChildren.length; i++) {
- var child = desktop.visibleChildren[i];
- if (child.hasOwnProperty("modality")) {
- var mappedPoint = mapToItem(child, point.x, point.y);
- if (child.hasOwnProperty("frame")) {
- var outLine = child.frame.children[2];
- var framePoint = outLine.mapFromGlobal(point.x, point.y);
- if (outLine.contains(framePoint)) {
- return true;
- }
- }
-
- if (child.contains(mappedPoint)) {
- return true;
- }
- }
- }
- return false;
- }
-
- function setPinned(newPinned) {
- pinned = newPinned
- }
-
- property real unpinnedAlpha: 1.0;
-
- Behavior on unpinnedAlpha {
- NumberAnimation {
- easing.type: Easing.Linear;
- duration: 300
- }
- }
-
- state: "NORMAL"
- states: [
- State {
- name: "NORMAL"
- PropertyChanges { target: desktop; unpinnedAlpha: 1.0 }
- },
- State {
- name: "PINNED"
- PropertyChanges { target: desktop; unpinnedAlpha: 0.0 }
- }
- ]
-
- transitions: [
- Transition {
- NumberAnimation { properties: "unpinnedAlpha"; duration: 300 }
- }
- ]
-
- onPinnedChanged: {
- if (pinned) {
- d.raiseWindow(desktop);
- desktop.focus = true;
- desktop.forceActiveFocus();
-
- // recalculate our non-pinned children
- hiddenChildren = d.findMatchingChildren(desktop, function(child){
- return !d.isTopLevelWindow(child) && child.visible && !child.pinned;
- });
-
- hiddenChildren.forEach(function(child){
- child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha });
- });
- }
- state = pinned ? "PINNED" : "NORMAL"
- }
-
- onShowDesktop: pinned = false
-
- function raise(item) {
- var targetWindow = d.getDesktopWindow(item);
- if (!targetWindow) {
- console.warn("Could not find top level window for " + item);
- return;
- }
-
- // Fix up the Z-order (takes into account if this is a modal window)
- d.raiseWindow(targetWindow);
- var setFocus = true;
- if (!d.isModalWindow(targetWindow)) {
- var modalWindows = d.getTopLevelWindows(d.isModalWindow);
- if (modalWindows.length) {
- setFocus = false;
- }
- }
-
- if (setFocus) {
- targetWindow.focus = true;
- }
-
- showDesktop();
- }
-
- function ensureTitleBarVisible(targetWindow) {
- // Reposition window to ensure that title bar is vertically inside window.
- if (targetWindow.frame && targetWindow.frame.decoration) {
- var topMargin = -targetWindow.frame.decoration.anchors.topMargin; // Frame's topMargin is a negative value.
- targetWindow.y = Math.max(targetWindow.y, topMargin);
- }
- }
-
- function centerOnVisible(item) {
- var targetWindow = d.getDesktopWindow(item);
- if (!targetWindow) {
- console.warn("Could not find top level window for " + item);
- return;
- }
-
- if (typeof Controller === "undefined") {
- console.warn("Controller not yet available... can't center");
- return;
- }
-
- var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect();
- var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y,
- newRecommendedRectJS.width,
- newRecommendedRectJS.height);
- var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
- var newX = newRecommendedRect.x + ((newRecommendedRect.width - targetWindow.width) / 2);
- var newY = newRecommendedRect.y + ((newRecommendedRect.height - targetWindow.height) / 2);
- targetWindow.x = newX;
- targetWindow.y = newY;
-
- ensureTitleBarVisible(targetWindow);
-
- // If we've noticed that our recommended desktop rect has changed, record that change here.
- if (recommendedRect != newRecommendedRect) {
- recommendedRect = newRecommendedRect;
- }
- }
-
- function repositionOnVisible(item) {
- var targetWindow = d.getDesktopWindow(item);
- if (!targetWindow) {
- console.warn("Could not find top level window for " + item);
- return;
- }
-
- if (typeof Controller === "undefined") {
- console.warn("Controller not yet available... can't reposition targetWindow:" + targetWindow);
- return;
- }
-
- var oldRecommendedRect = recommendedRect;
- var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
- var newRecommendedRect = Controller.getRecommendedHUDRect();
- var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
- repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
- }
-
- function repositionWindow(targetWindow, forceReposition,
- oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions) {
-
- if (desktop.width === 0 || desktop.height === 0) {
- return;
- }
-
- if (!targetWindow) {
- console.warn("Could not find top level window for " + item);
- return;
- }
-
- var recommended = Controller.getRecommendedHUDRect();
- var maxX = recommended.x + recommended.width;
- var maxY = recommended.y + recommended.height;
- var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y);
-
- // if we asked to force reposition, or if the window is completely outside of the recommended rectangle, reposition it
- if (forceReposition || (targetWindow.x > maxX || (targetWindow.x + targetWindow.width) < recommended.x) ||
- (targetWindow.y > maxY || (targetWindow.y + targetWindow.height) < recommended.y)) {
- newPosition.x = -1
- newPosition.y = -1
- }
-
- if (newPosition.x === -1 && newPosition.y === -1) {
- var originRelativeX = (targetWindow.x - oldRecommendedRect.x);
- var originRelativeY = (targetWindow.y - oldRecommendedRect.y);
- if (isNaN(originRelativeX)) {
- originRelativeX = 0;
- }
- if (isNaN(originRelativeY)) {
- originRelativeY = 0;
- }
- var fractionX = Utils.clamp(originRelativeX / oldRecommendedDimmensions.x, 0, 1);
- var fractionY = Utils.clamp(originRelativeY / oldRecommendedDimmensions.y, 0, 1);
- var newX = (fractionX * newRecommendedDimmensions.x) + newRecommendedRect.x;
- var newY = (fractionY * newRecommendedDimmensions.y) + newRecommendedRect.y;
- newPosition = Qt.vector2d(newX, newY);
- }
- targetWindow.x = newPosition.x;
- targetWindow.y = newPosition.y;
-
- ensureTitleBarVisible(targetWindow);
- }
-
- Component { id: messageDialogBuilder; MessageDialog { } }
- function messageBox(properties) {
- return messageDialogBuilder.createObject(desktop, properties);
- }
-
- Component { id: inputDialogBuilder; QueryDialog { } }
- function inputDialog(properties) {
- return inputDialogBuilder.createObject(desktop, properties);
- }
-
- Component { id: customInputDialogBuilder; CustomQueryDialog { } }
- function customInputDialog(properties) {
- return customInputDialogBuilder.createObject(desktop, properties);
- }
-
- Component { id: fileDialogBuilder; FileDialog { } }
- function fileDialog(properties) {
- return fileDialogBuilder.createObject(desktop, properties);
- }
-
- Component { id: assetDialogBuilder; AssetDialog { } }
- function assetDialog(properties) {
- return assetDialogBuilder.createObject(desktop, properties);
- }
-
- function unfocusWindows() {
- // First find the active focus item, and unfocus it, all the way
- // up the parent chain to the window
- var currentFocus = offscreenWindow.activeFocusItem;
- var targetWindow = d.getDesktopWindow(currentFocus);
- while (currentFocus) {
- if (currentFocus === targetWindow) {
- break;
- }
- currentFocus.focus = false;
- currentFocus = currentFocus.parent;
- }
-
- // Unfocus all windows
- var windows = d.getTopLevelWindows();
- for (var i = 0; i < windows.length; ++i) {
- windows[i].focus = false;
- }
-
- // For the desktop to have active focus
- desktop.focus = true;
- desktop.forceActiveFocus();
- }
-
- function openBrowserWindow(request, profile) {
- var component = Qt.createComponent("../Browser.qml");
- var newWindow = component.createObject(desktop);
- newWindow.webView.profile = profile;
- request.openIn(newWindow.webView);
- }
-
- FocusHack { id: focusHack; }
-
- Rectangle {
- id: focusDebugger;
- objectName: "focusDebugger"
- z: 9999; visible: false; color: "red"
- ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 }
- }
-
- Action {
- text: "Toggle Focus Debugger"
- shortcut: "Ctrl+Shift+F"
- enabled: DebugQML
- onTriggered: focusDebugger.visible = !focusDebugger.visible
- }
-
-}
diff --git a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml b/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml
deleted file mode 100644
index aadd7c88ae..0000000000
--- a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml
+++ /dev/null
@@ -1,338 +0,0 @@
-//
-// CustomQueryDialog.qml
-//
-// Created by Zander Otavka on 7/14/16
-// 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.7;
-import QtQuick.Dialogs 1.2 as OriginalDialogs;
-import QtQuick.Controls 1.4;
-
-import "../controls-uit";
-import "../styles-uit";
-import "../windows";
-
-ModalWindow {
- id: root;
- HifiConstants { id: hifi; }
- implicitWidth: 640;
- implicitHeight: 320;
- visible: true;
- keyboardOverride: true // Disable ModalWindow's keyboard.
-
- signal selected(var result);
- signal canceled();
-
- property int icon: hifi.icons.none;
- property string iconText: "";
- property int iconSize: 35;
- onIconChanged: updateIcon();
-
- property var textInput;
- property var comboBox;
- property var checkBox;
- onTextInputChanged: {
- if (textInput && textInput.text !== undefined) {
- textField.text = textInput.text;
- }
- }
- onComboBoxChanged: {
- if (comboBox && comboBox.index !== undefined) {
- comboBoxField.currentIndex = comboBox.index;
- }
- }
- onCheckBoxChanged: {
- if (checkBox && checkBox.checked !== undefined) {
- checkBoxField.checked = checkBox.checked;
- }
- }
-
- property bool keyboardEnabled: false
- property bool keyboardRaised: false
- property bool punctuationMode: false
- onKeyboardRaisedChanged: d.resize();
-
- property var warning: "";
- property var result;
-
- property var implicitCheckState: null;
-
- property int titleWidth: 0;
- onTitleWidthChanged: d.resize();
-
- function updateIcon() {
- if (!root) {
- return;
- }
- iconText = hifi.glyphForIcon(root.icon);
- }
-
- function updateCheckbox() {
- if (checkBox.disableForItems) {
- var currentItemInDisableList = false;
- for (var i in checkBox.disableForItems) {
- if (comboBoxField.currentIndex === checkBox.disableForItems[i]) {
- currentItemInDisableList = true;
- break;
- }
- }
-
- if (currentItemInDisableList) {
- checkBoxField.enabled = false;
- if (checkBox.checkStateOnDisable !== null && checkBox.checkStateOnDisable !== undefined) {
- root.implicitCheckState = checkBoxField.checked;
- checkBoxField.checked = checkBox.checkStateOnDisable;
- }
- root.warning = checkBox.warningOnDisable;
- } else {
- checkBoxField.enabled = true;
- if (root.implicitCheckState !== null) {
- checkBoxField.checked = root.implicitCheckState;
- root.implicitCheckState = null;
- }
- root.warning = "";
- }
- }
- }
-
- Item {
- clip: true;
- width: pane.width;
- height: pane.height;
- anchors.margins: 0;
-
- QtObject {
- id: d;
- readonly property int minWidth: 480
- readonly property int maxWdith: 1280
- readonly property int minHeight: 120
- readonly property int maxHeight: 720
-
- function resize() {
- var targetWidth = Math.max(titleWidth, pane.width);
- var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) +
- (extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) +
- (buttons.height + 3 * hifi.dimensions.contentSpacing.y) +
- ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + hifi.dimensions.contentSpacing.y) : 0);
-
- root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
- root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ?
- d.maxHeight : targetHeight);
- }
- }
-
- Item {
- anchors {
- top: parent.top;
- bottom: extraInputs.visible ? extraInputs.top : buttons.top;
- left: parent.left;
- right: parent.right;
- margins: 0;
- }
-
- // FIXME make a text field type that can be bound to a history for autocompletion
- TextField {
- id: textField;
- label: root.textInput.label;
- focus: root.textInput ? true : false;
- visible: root.textInput ? true : false;
- anchors {
- left: parent.left;
- right: parent.right;
- bottom: keyboard.top;
- bottomMargin: hifi.dimensions.contentSpacing.y;
- }
- }
-
- Keyboard {
- id: keyboard
- raised: keyboardEnabled && keyboardRaised
- numeric: punctuationMode
- anchors {
- left: parent.left
- right: parent.right
- bottom: parent.bottom
- bottomMargin: raised ? hifi.dimensions.contentSpacing.y : 0
- }
- }
- }
-
- Item {
- id: extraInputs;
- visible: Boolean(root.checkBox || root.comboBox);
- anchors {
- left: parent.left;
- right: parent.right;
- bottom: buttons.top;
- bottomMargin: hifi.dimensions.contentSpacing.y;
- }
- height: comboBoxField.controlHeight;
- onHeightChanged: d.resize();
- onWidthChanged: d.resize();
-
- CheckBox {
- id: checkBoxField;
- text: root.checkBox.label;
- focus: Boolean(root.checkBox);
- visible: Boolean(root.checkBox);
- anchors {
- left: parent.left;
- bottom: parent.bottom;
- leftMargin: 6; // Magic number to align with warning icon
- bottomMargin: 6;
- }
- }
-
- ComboBox {
- id: comboBoxField;
- label: root.comboBox.label;
- focus: Boolean(root.comboBox);
- visible: Boolean(root.comboBox);
- Binding on x {
- when: comboBoxField.visible
- value: !checkBoxField.visible ? buttons.x : acceptButton.x
- }
-
- Binding on width {
- when: comboBoxField.visible
- value: !checkBoxField.visible ? buttons.width : buttons.width - acceptButton.x
- }
- anchors {
- right: parent.right;
- bottom: parent.bottom;
- }
- model: root.comboBox ? root.comboBox.items : [];
- onAccepted: {
- updateCheckbox();
- focus = true;
- }
- }
- }
-
- Row {
- id: buttons;
- focus: true;
- spacing: hifi.dimensions.contentSpacing.x;
- layoutDirection: Qt.RightToLeft;
- onHeightChanged: d.resize();
- onWidthChanged: {
- d.resize();
- resizeWarningText();
- }
-
- anchors {
- bottom: parent.bottom;
- left: parent.left;
- right: parent.right;
- bottomMargin: hifi.dimensions.contentSpacing.y;
- }
-
- function resizeWarningText() {
- var rowWidth = buttons.width;
- var buttonsWidth = acceptButton.width + cancelButton.width + hifi.dimensions.contentSpacing.x * 2;
- var warningIconWidth = warningIcon.width + hifi.dimensions.contentSpacing.x;
- warningText.width = rowWidth - buttonsWidth - warningIconWidth;
- }
-
- Button {
- id: cancelButton;
- action: cancelAction;
- }
-
- Button {
- id: acceptButton;
- action: acceptAction;
- }
-
- Text {
- id: warningText;
- visible: Boolean(root.warning);
- text: root.warning;
- wrapMode: Text.WordWrap;
- font.italic: true;
- maximumLineCount: 3;
- }
-
- HiFiGlyphs {
- id: warningIcon;
- visible: Boolean(root.warning);
- text: hifi.glyphs.alert;
- size: hifi.dimensions.controlLineHeight;
- width: 20 // Line up with checkbox.
- }
- }
-
- Action {
- id: cancelAction;
- text: qsTr("Cancel");
- shortcut: "Esc";
- onTriggered: {
- root.result = null;
- root.canceled();
- // FIXME we are leaking memory to avoid a crash
- // root.destroy();
-
- root.disableFade = true
- visible = false;
- }
- }
-
- Action {
- id: acceptAction;
- text: qsTr("Add");
- shortcut: "Return"
- onTriggered: {
- var result = {};
- if (textInput) {
- result.textInput = textField.text;
- }
- if (comboBox) {
- result.comboBox = comboBoxField.currentIndex;
- result.comboBoxText = comboBoxField.currentText;
- }
- if (checkBox) {
- result.checkBox = checkBoxField.enabled ? checkBoxField.checked : null;
- }
- root.result = JSON.stringify(result);
- root.selected(root.result);
- // FIXME we are leaking memory to avoid a crash
- // root.destroy();
-
- root.disableFade = true
- visible = false;
- }
- }
- }
-
- Keys.onPressed: {
- if (!visible) {
- return;
- }
-
- switch (event.key) {
- case Qt.Key_Escape:
- case Qt.Key_Back:
- cancelAction.trigger();
- event.accepted = true;
- break;
-
- case Qt.Key_Return:
- case Qt.Key_Enter:
- acceptAction.trigger();
- event.accepted = true;
- break;
- }
- }
-
- Component.onCompleted: {
- keyboardEnabled = HMD.active;
- updateIcon();
- updateCheckbox();
- d.resize();
- textField.forceActiveFocus();
- }
-}
diff --git a/interface/resources/qml/dialogs/+android/FileDialog.qml b/interface/resources/qml/dialogs/+android/FileDialog.qml
deleted file mode 100644
index be6524d2b8..0000000000
--- a/interface/resources/qml/dialogs/+android/FileDialog.qml
+++ /dev/null
@@ -1,840 +0,0 @@
-//
-// FileDialog.qml
-//
-// Created by Bradley Austin Davis on 14 Jan 2016
-// Copyright 2015 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.7
-import Qt.labs.folderlistmodel 2.1
-import Qt.labs.settings 1.0
-import QtQuick.Dialogs 1.2 as OriginalDialogs
-import QtQuick.Controls 1.4
-
-import ".."
-import "../controls-uit"
-import "../styles-uit"
-import "../windows"
-
-import "fileDialog"
-
-//FIXME implement shortcuts for favorite location
-ModalWindow {
- id: root
- resizable: true
- implicitWidth: 480
- implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0)
-
- minSize: Qt.vector2d(360, 240)
- draggable: true
-
- HifiConstants { id: hifi }
-
- property var filesModel: ListModel { }
-
- Settings {
- category: "FileDialog"
- property alias width: root.width
- property alias height: root.height
- property alias x: root.x
- property alias y: root.y
- }
-
-
- // Set from OffscreenUi::getOpenFile()
- property alias caption: root.title;
- // Set from OffscreenUi::getOpenFile()
- property alias dir: fileTableModel.folder;
- // Set from OffscreenUi::getOpenFile()
- property alias filter: selectionType.filtersString;
- // Set from OffscreenUi::getOpenFile()
- property int options; // <-- FIXME unused
-
- property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : ""
- property int iconSize: 40
-
- property bool selectDirectory: false;
- property bool showHidden: true;
- // FIXME implement
- property bool multiSelect: false;
- property bool saveDialog: false;
- property var helper: fileDialogHelper
- property alias model: fileTableView.model
- property var drives: helper.drives()
-
- property int titleWidth: 0
-
- signal selectedFile(var file);
- signal canceled();
- signal selected(int button);
- function click(button) {
- clickedButton = button;
- selected(button);
- destroy();
- }
-
- property int clickedButton: OriginalDialogs.StandardButton.NoButton;
-
- Component.onCompleted: {
- console.log("Helper " + helper + " drives " + drives);
-
- fileDialogItem.keyboardEnabled = HMD.active;
-
- // HACK: The following lines force the model to initialize properly such that the go-up button
- // works properly from the initial screen.
- var initialFolder = folderListModel.folder;
- fileTableModel.folder = helper.pathToUrl(drives[0]);
- fileTableModel.folder = initialFolder;
-
- iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";
-
- // Clear selection when click on external frame.
- frameClicked.connect(function() { d.clearSelection(); });
-
- if (selectDirectory) {
- currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
- d.currentSelectionIsFolder = true;
- d.currentSelectionUrl = initialFolder;
- }
-
- helper.contentsChanged.connect(function() {
- if (folderListModel) {
- // Make folderListModel refresh.
- var save = folderListModel.folder;
- folderListModel.folder = "";
- folderListModel.folder = save;
- }
- });
-
- focusTimer.start();
- }
-
- Timer {
- id: focusTimer
- interval: 10
- running: false
- repeat: false
- onTriggered: {
- fileTableView.contentItem.forceActiveFocus();
- }
- }
-
- Item {
- id: fileDialogItem
- clip: true
- width: pane.width
- height: pane.height
- anchors.margins: 0
-
- property bool keyboardEnabled: false
- property bool keyboardRaised: false
- property bool punctuationMode: false
-
- MouseArea {
- // Clear selection when click on internal unused area.
- anchors.fill: parent
- drag.target: root
- onClicked: {
- d.clearSelection();
- // Defocus text field so that the keyboard gets hidden.
- // Clicking also breaks keyboard navigation apart from backtabbing to cancel
- frame.forceActiveFocus();
- }
- }
-
- Row {
- id: navControls
- anchors {
- top: parent.top
- topMargin: hifi.dimensions.contentMargin.y
- left: parent.left
- }
- spacing: hifi.dimensions.contentSpacing.x
-
- GlyphButton {
- id: upButton
- glyph: hifi.glyphs.levelUp
- width: height
- size: 30
- enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
- onClicked: d.navigateUp();
- Keys.onReturnPressed: { d.navigateUp(); }
- KeyNavigation.tab: homeButton
- KeyNavigation.backtab: upButton
- KeyNavigation.left: upButton
- KeyNavigation.right: homeButton
- }
-
- GlyphButton {
- id: homeButton
- property var destination: helper.home();
- glyph: hifi.glyphs.home
- size: 28
- width: height
- enabled: d.homeDestination ? true : false
- onClicked: d.navigateHome();
- Keys.onReturnPressed: { d.navigateHome(); }
- KeyNavigation.tab: fileTableView.contentItem
- KeyNavigation.backtab: upButton
- KeyNavigation.left: upButton
- }
- }
-
- ComboBox {
- id: pathSelector
- anchors {
- top: parent.top
- topMargin: hifi.dimensions.contentMargin.y
- left: navControls.right
- leftMargin: hifi.dimensions.contentSpacing.x
- right: parent.right
- }
-
- property var lastValidFolder: helper.urlToPath(fileTableModel.folder)
-
- function calculatePathChoices(folder) {
- var folders = folder.split("/"),
- choices = [],
- i, length;
-
- if (folders[folders.length - 1] === "") {
- folders.pop();
- }
-
- choices.push(folders[0]);
-
- for (i = 1, length = folders.length; i < length; i++) {
- choices.push(choices[i - 1] + "/" + folders[i]);
- }
-
- if (folders[0] === "") {
- // Special handling for OSX root dir.
- choices[0] = "/";
- }
-
- choices.reverse();
-
- if (drives && drives.length > 1) {
- choices.push("This PC");
- }
-
- if (choices.length > 0) {
- pathSelector.model = choices;
- }
- }
-
- onLastValidFolderChanged: {
- var folder = d.capitalizeDrive(lastValidFolder);
- calculatePathChoices(folder);
- }
-
- onCurrentTextChanged: {
- var folder = currentText;
-
- if (/^[a-zA-z]:$/.test(folder)) {
- folder = "file:///" + folder + "/";
- } else if (folder === "This PC") {
- folder = "file:///";
- } else {
- folder = helper.pathToUrl(folder);
- }
-
- if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
- if (root.selectDirectory) {
- currentSelection.text = currentText !== "This PC" ? currentText : "";
- d.currentSelectionUrl = helper.pathToUrl(currentText);
- }
- fileTableModel.folder = folder;
- }
- }
-
- KeyNavigation.up: fileTableView.contentItem
- KeyNavigation.down: fileTableView.contentItem
- KeyNavigation.tab: fileTableView.contentItem
- KeyNavigation.backtab: fileTableView.contentItem
- KeyNavigation.left: fileTableView.contentItem
- KeyNavigation.right: fileTableView.contentItem
- }
-
- QtObject {
- id: d
- property var currentSelectionUrl;
- readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl);
- property bool currentSelectionIsFolder;
- property var backStack: []
- property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
- property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
- property var homeDestination: helper.home();
-
- function capitalizeDrive(path) {
- // Consistently capitalize drive letter for Windows.
- if (/[a-zA-Z]:/.test(path)) {
- return path.charAt(0).toUpperCase() + path.slice(1);
- }
- return path;
- }
-
- function update() {
- var row = fileTableView.currentRow;
-
- if (row === -1) {
- if (!root.selectDirectory) {
- currentSelection.text = "";
- currentSelectionIsFolder = false;
- }
- return;
- }
-
- currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
- currentSelectionIsFolder = fileTableView.model !== filesModel ?
- fileTableView.model.isFolder(row) :
- fileTableModel.isFolder(row);
- if (root.selectDirectory || !currentSelectionIsFolder) {
- currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
- } else {
- currentSelection.text = "";
- }
- }
-
- function navigateUp() {
- if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
- fileTableModel.folder = fileTableModel.parentFolder;
- return true;
- }
- }
-
- function navigateHome() {
- fileTableModel.folder = homeDestination;
- return true;
- }
-
- function clearSelection() {
- fileTableView.selection.clear();
- fileTableView.currentRow = -1;
- update();
- }
- }
-
- FolderListModel {
- id: folderListModel
- nameFilters: selectionType.currentFilter
- showDirsFirst: true
- showDotAndDotDot: false
- showFiles: !root.selectDirectory
- showHidden: root.showHidden
- Component.onCompleted: {
- showFiles = !root.selectDirectory
- showHidden = root.showHidden
- }
-
- onFolderChanged: {
- d.clearSelection();
- fileTableModel.update(); // Update once the data from the folder change is available.
- }
-
- function getItem(index, field) {
- return get(index, field);
- }
- }
-
- ListModel {
- // Emulates FolderListModel but contains drive data.
- id: driveListModel
-
- property int count: 1
-
- Component.onCompleted: initialize();
-
- function initialize() {
- var drive,
- i;
-
- count = drives.length;
-
- for (i = 0; i < count; i++) {
- drive = drives[i].slice(0, -1); // Remove trailing "/".
- append({
- fileName: drive,
- fileModified: new Date(0),
- fileSize: 0,
- filePath: drive + "/",
- fileIsDir: true,
- fileNameSort: drive.toLowerCase()
- });
- }
- }
-
- function getItem(index, field) {
- return get(index)[field];
- }
- }
-
- Component {
- id: filesModelBuilder
- ListModel { }
- }
-
- QtObject {
- id: fileTableModel
-
- // FolderListModel has a couple of problems:
- // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
- // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
- //
- // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
- // drive information when viewing at the computer level.
-
- property var folder
- property int sortOrder: Qt.AscendingOrder
- property int sortColumn: 0
- property var model: folderListModel
- property string parentFolder: calculateParentFolder();
-
- readonly property string rootFolder: "file:///"
-
- function calculateParentFolder() {
- if (model === folderListModel) {
- if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
- return rootFolder;
- } else {
- return folderListModel.parentFolder;
- }
- } else {
- return "";
- }
- }
-
- onFolderChanged: {
- if (folder === rootFolder) {
- model = driveListModel;
- helper.monitorDirectory("");
- update();
- } else {
- var needsUpdate = model === driveListModel && folder === folderListModel.folder;
-
- model = folderListModel;
- folderListModel.folder = folder;
- helper.monitorDirectory(helper.urlToPath(folder));
-
- if (needsUpdate) {
- update();
- }
- }
- }
-
- function isFolder(row) {
- if (row === -1) {
- return false;
- }
- return filesModel.get(row).fileIsDir;
- }
-
- function get(row) {
- return filesModel.get(row)
- }
-
- function update() {
- var dataFields = ["fileName", "fileModified", "fileSize"],
- sortFields = ["fileNameSort", "fileModified", "fileSize"],
- dataField = dataFields[sortColumn],
- sortField = sortFields[sortColumn],
- sortValue,
- fileName,
- fileIsDir,
- comparisonFunction,
- lower,
- middle,
- upper,
- rows = 0,
- i;
-
- filesModel = filesModelBuilder.createObject(root);
-
- comparisonFunction = sortOrder === Qt.AscendingOrder
- ? function(a, b) { return a < b; }
- : function(a, b) { return a > b; }
-
- for (i = 0; i < model.count; i++) {
- fileName = model.getItem(i, "fileName");
- fileIsDir = model.getItem(i, "fileIsDir");
-
- sortValue = model.getItem(i, dataField);
- if (dataField === "fileName") {
- // Directories first by prefixing a "*".
- // Case-insensitive.
- sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
- }
-
- lower = 0;
- upper = rows;
- while (lower < upper) {
- middle = Math.floor((lower + upper) / 2);
- var lessThan;
- if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
- lessThan = true;
- upper = middle;
- } else {
- lessThan = false;
- lower = middle + 1;
- }
- }
-
- filesModel.insert(lower, {
- fileName: fileName,
- fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
- fileSize: model.getItem(i, "fileSize"),
- filePath: model.getItem(i, "filePath"),
- fileIsDir: fileIsDir,
- fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
- });
-
- rows++;
- }
- }
- }
-
- Table {
- id: fileTableView
- colorScheme: hifi.colorSchemes.light
- anchors {
- top: navControls.bottom
- topMargin: hifi.dimensions.contentSpacing.y
- left: parent.left
- right: parent.right
- bottom: currentSelection.top
- bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
- }
- headerVisible: !selectDirectory
- onDoubleClicked: navigateToRow(row);
- Keys.onReturnPressed: navigateToCurrentRow();
- Keys.onEnterPressed: navigateToCurrentRow();
-
- sortIndicatorColumn: 0
- sortIndicatorOrder: Qt.AscendingOrder
- sortIndicatorVisible: true
-
- model: filesModel
-
- function updateSort() {
- fileTableModel.sortOrder = sortIndicatorOrder;
- fileTableModel.sortColumn = sortIndicatorColumn;
- fileTableModel.update();
- }
-
- onSortIndicatorColumnChanged: { updateSort(); }
-
- onSortIndicatorOrderChanged: { updateSort(); }
-
- itemDelegate: Item {
- clip: true
-
- FiraSansSemiBold {
- text: getText();
- elide: styleData.elideMode
- anchors {
- left: parent.left
- leftMargin: hifi.dimensions.tablePadding
- right: parent.right
- rightMargin: hifi.dimensions.tablePadding
- verticalCenter: parent.verticalCenter
- }
- size: hifi.fontSizes.tableText
- color: hifi.colors.baseGrayHighlight
- font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
- ? "Fira Sans SemiBold" : "Fira Sans"
-
- function getText() {
- if (styleData.row === -1) {
- return styleData.value;
- }
-
- switch (styleData.column) {
- case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
- case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
- default: return styleData.value;
- }
- }
- function formatSize(size) {
- var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
- var suffixIndex = 0
- while ((size / 1024.0) > 1.1) {
- size /= 1024.0;
- ++suffixIndex;
- }
-
- size = Math.round(size*1000)/1000;
- size = size.toLocaleString()
-
- return size + " " + suffixes[suffixIndex];
- }
- }
- }
-
- TableViewColumn {
- id: fileNameColumn
- role: "fileName"
- title: "Name"
- width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
- movable: false
- resizable: true
- }
- TableViewColumn {
- id: fileModifiedColumn
- role: "fileModified"
- title: "Date"
- width: 0.3 * fileTableView.width
- movable: false
- resizable: true
- visible: !selectDirectory
- }
- TableViewColumn {
- role: "fileSize"
- title: "Size"
- width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
- movable: false
- resizable: true
- visible: !selectDirectory
- }
-
- function navigateToRow(row) {
- currentRow = row;
- navigateToCurrentRow();
- }
-
- function navigateToCurrentRow() {
- var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel
- var row = fileTableView.currentRow
- var isFolder = currentModel.isFolder(row);
- var file = currentModel.get(row).filePath;
- if (isFolder) {
- currentModel.folder = helper.pathToUrl(file);
- } else {
- okAction.trigger();
- }
- }
-
- property string prefix: ""
-
- function addToPrefix(event) {
- if (!event.text || event.text === "") {
- return false;
- }
- var newPrefix = prefix + event.text.toLowerCase();
- var matchedIndex = -1;
- for (var i = 0; i < model.count; ++i) {
- var name = model !== filesModel ? model.get(i).fileName.toLowerCase() :
- filesModel.get(i).fileName.toLowerCase();
- if (0 === name.indexOf(newPrefix)) {
- matchedIndex = i;
- break;
- }
- }
-
- if (matchedIndex !== -1) {
- fileTableView.selection.clear();
- fileTableView.selection.select(matchedIndex);
- fileTableView.currentRow = matchedIndex;
- fileTableView.prefix = newPrefix;
- }
- prefixClearTimer.restart();
- return true;
- }
-
- Timer {
- id: prefixClearTimer
- interval: 1000
- repeat: false
- running: false
- onTriggered: fileTableView.prefix = "";
- }
-
- Keys.onPressed: {
- switch (event.key) {
- case Qt.Key_Backspace:
- case Qt.Key_Tab:
- case Qt.Key_Backtab:
- event.accepted = false;
- break;
- case Qt.Key_Escape:
- event.accepted = true;
- root.click(OriginalDialogs.StandardButton.Cancel);
- break;
- default:
- if (addToPrefix(event)) {
- event.accepted = true
- } else {
- event.accepted = false;
- }
- break;
- }
- }
-
- KeyNavigation.tab: root.saveDialog ? currentSelection : openButton
- }
-
- TextField {
- id: currentSelection
- label: selectDirectory ? "Directory:" : "File name:"
- anchors {
- left: parent.left
- right: selectionType.visible ? selectionType.left: parent.right
- rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
- bottom: keyboard.top
- bottomMargin: hifi.dimensions.contentSpacing.y
- }
- readOnly: !root.saveDialog
- activeFocusOnTab: !readOnly
- onActiveFocusChanged: if (activeFocus) { selectAll(); }
- onAccepted: okAction.trigger();
- KeyNavigation.up: fileTableView.contentItem
- KeyNavigation.down: openButton
- KeyNavigation.tab: openButton
- KeyNavigation.backtab: fileTableView.contentItem
- }
-
- FileTypeSelection {
- id: selectionType
- anchors {
- top: currentSelection.top
- left: buttonRow.left
- right: parent.right
- }
- visible: !selectDirectory && filtersCount > 1
- }
-
- Keyboard {
- id: keyboard
- raised: parent.keyboardEnabled && parent.keyboardRaised
- numeric: parent.punctuationMode
- anchors {
- left: parent.left
- right: parent.right
- bottom: buttonRow.top
- bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
- }
- }
-
- Row {
- id: buttonRow
- anchors {
- right: parent.right
- bottom: parent.bottom
- }
- spacing: hifi.dimensions.contentSpacing.y
-
- Button {
- id: openButton
- color: hifi.buttons.blue
- action: okAction
- Keys.onReturnPressed: okAction.trigger()
- KeyNavigation.right: cancelButton
- KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
- KeyNavigation.tab: cancelButton
- }
-
- Button {
- id: cancelButton
- action: cancelAction
- Keys.onReturnPressed: { cancelAction.trigger() }
- KeyNavigation.left: openButton
- KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
- KeyNavigation.backtab: openButton
- }
- }
-
- Action {
- id: okAction
- text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
- enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
- onTriggered: {
- if (!root.selectDirectory && !d.currentSelectionIsFolder
- || root.selectDirectory && fileTableView.currentRow === -1) {
- okActionTimer.start();
- } else {
- fileTableView.navigateToCurrentRow();
- }
- }
- }
-
- Timer {
- id: okActionTimer
- interval: 50
- running: false
- repeat: false
- onTriggered: {
- if (!root.saveDialog) {
- selectedFile(d.currentSelectionUrl);
- root.destroy()
- return;
- }
-
- // Handle the ambiguity between different cases
- // * typed name (with or without extension)
- // * full path vs relative vs filename only
- var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter);
-
- if (!selection) {
- desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" })
- return;
- }
-
- if (helper.urlIsDir(selection)) {
- root.dir = selection;
- currentSelection.text = "";
- return;
- }
-
- // Check if the file is a valid target
- if (!helper.urlIsWritable(selection)) {
- desktop.messageBox({
- icon: OriginalDialogs.StandardIcon.Warning,
- text: "Unable to write to location " + selection
- })
- return;
- }
-
- if (helper.urlExists(selection)) {
- var messageBox = desktop.messageBox({
- icon: OriginalDialogs.StandardIcon.Question,
- buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
- text: "Do you wish to overwrite " + selection + "?",
- });
- var result = messageBox.exec();
- if (OriginalDialogs.StandardButton.Yes !== result) {
- return;
- }
- }
-
- console.log("Selecting " + selection)
- selectedFile(selection);
- root.destroy();
- }
- }
-
- Action {
- id: cancelAction
- text: "Cancel"
- onTriggered: { canceled(); root.shown = false; }
- }
- }
-
- Keys.onPressed: {
- switch (event.key) {
- case Qt.Key_Backspace:
- event.accepted = d.navigateUp();
- break;
-
- case Qt.Key_Home:
- event.accepted = d.navigateHome();
- break;
-
- case Qt.Key_Escape:
- event.accepted = true;
- root.click(OriginalDialogs.StandardButton.Cancel);
- break;
- }
- }
-}
diff --git a/interface/resources/qml/dialogs/+android/QueryDialog.qml b/interface/resources/qml/dialogs/+android/QueryDialog.qml
deleted file mode 100644
index aec6d8a286..0000000000
--- a/interface/resources/qml/dialogs/+android/QueryDialog.qml
+++ /dev/null
@@ -1,231 +0,0 @@
-//
-// QueryDialog.qml
-//
-// Created by Bradley Austin Davis on 22 Jan 2016
-// Copyright 2015 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.7
-import QtQuick.Controls 1.4
-
-import "../controls-uit"
-import "../styles-uit"
-import "../windows"
-
-ModalWindow {
- id: root
- HifiConstants { id: hifi }
- implicitWidth: 640
- implicitHeight: 320
- visible: true
- keyboardOverride: true // Disable ModalWindow's keyboard.
-
- signal selected(var result);
- signal canceled();
-
- property int icon: hifi.icons.none
- property string iconText: ""
- property int iconSize: 35
- onIconChanged: updateIcon();
-
- property var items;
- property string label
- property var result;
- property alias current: textResult.text
-
- // For text boxes
- property alias placeholderText: textResult.placeholderText
-
- // For combo boxes
- property bool editable: true;
-
- property int titleWidth: 0
- onTitleWidthChanged: d.resize();
-
- property bool keyboardEnabled: false
- property bool keyboardRaised: false
- property bool punctuationMode: false
-
- onKeyboardRaisedChanged: d.resize();
-
- function updateIcon() {
- if (!root) {
- return;
- }
- iconText = hifi.glyphForIcon(root.icon);
- }
-
- Item {
- id: modalWindowItem
- clip: true
- width: pane.width
- height: pane.height
- anchors.margins: 0
-
- QtObject {
- id: d
- readonly property int minWidth: 480
- readonly property int maxWdith: 1280
- readonly property int minHeight: 120
- readonly property int maxHeight: 720
-
- function resize() {
- var targetWidth = Math.max(titleWidth, pane.width)
- var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height
- root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
- root.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0)
- }
- }
-
- Item {
- anchors {
- top: parent.top
- bottom: keyboard.top;
- left: parent.left;
- right: parent.right;
- margins: 0
- bottomMargin: 2 * hifi.dimensions.contentSpacing.y
- }
-
- // FIXME make a text field type that can be bound to a history for autocompletion
- TextField {
- id: textResult
- label: root.label
- visible: items ? false : true
- anchors {
- left: parent.left;
- right: parent.right;
- bottom: parent.bottom
- }
- KeyNavigation.down: acceptButton
- KeyNavigation.tab: acceptButton
- }
-
- ComboBox {
- id: comboBox
- label: root.label
- visible: items ? true : false
- anchors {
- left: parent.left
- right: parent.right
- bottom: parent.bottom
- }
- model: items ? items : []
- KeyNavigation.down: acceptButton
- KeyNavigation.tab: acceptButton
- }
- }
-
- property alias keyboardOverride: root.keyboardOverride
- property alias keyboardRaised: root.keyboardRaised
- property alias punctuationMode: root.punctuationMode
- Keyboard {
- id: keyboard
- raised: keyboardEnabled && keyboardRaised
- numeric: punctuationMode
- anchors {
- left: parent.left
- right: parent.right
- bottom: buttons.top
- bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0
- }
- }
-
- Flow {
- id: buttons
- spacing: hifi.dimensions.contentSpacing.x
- onHeightChanged: d.resize(); onWidthChanged: d.resize();
- layoutDirection: Qt.RightToLeft
- anchors {
- bottom: parent.bottom
- right: parent.right
- margins: 0
- bottomMargin: hifi.dimensions.contentSpacing.y
- }
- Button {
- id: cancelButton
- action: cancelAction
- KeyNavigation.left: acceptButton
- KeyNavigation.up: items ? comboBox : textResult
- KeyNavigation.backtab: acceptButton
- }
- Button {
- id: acceptButton
- action: acceptAction
- KeyNavigation.right: cancelButton
- KeyNavigation.up: items ? comboBox : textResult
- KeyNavigation.tab: cancelButton
- KeyNavigation.backtab: items ? comboBox : textResult
- }
- }
-
- Action {
- id: cancelAction
- text: qsTr("Cancel");
- shortcut: "Esc"
- onTriggered: {
- root.canceled();
- // FIXME we are leaking memory to avoid a crash
- // root.destroy();
-
- root.disableFade = true
- visible = false;
- }
- }
-
- Action {
- id: acceptAction
- text: qsTr("OK");
- shortcut: "Return"
- onTriggered: {
- root.result = items ? comboBox.currentText : textResult.text
- root.selected(root.result);
- // FIXME we are leaking memory to avoid a crash
- // root.destroy();
-
- root.disableFade = true
- visible = false;
- }
- }
- }
-
- Keys.onPressed: {
- if (!visible) {
- return
- }
-
- switch (event.key) {
- case Qt.Key_Escape:
- case Qt.Key_Back:
- cancelAction.trigger()
- event.accepted = true;
- break;
-
- case Qt.Key_Return:
- case Qt.Key_Enter:
- if (acceptButton.focus) {
- acceptAction.trigger()
- } else if (cancelButton.focus) {
- cancelAction.trigger()
- } else if (comboBox.focus || comboBox.popup.focus) {
- comboBox.showList()
- }
- event.accepted = true;
- break;
- }
- }
-
- Component.onCompleted: {
- keyboardEnabled = HMD.active;
- updateIcon();
- d.resize();
- if (items) {
- comboBox.forceActiveFocus()
- } else {
- textResult.forceActiveFocus()
- }
- }
-}
diff --git a/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml
deleted file mode 100644
index 54bdb0a888..0000000000
--- a/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml
+++ /dev/null
@@ -1,533 +0,0 @@
-//
-// AssetDialogContent.qml
-//
-// Created by David Rowe on 19 Apr 2017
-// Copyright 2017 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-import QtQuick 2.7
-import QtQuick.Controls 1.5
-
-import "../../controls-uit"
-import "../../styles-uit"
-
-import "../fileDialog"
-
-Item {
- // Set from OffscreenUi::assetDialog()
- property alias dir: assetTableModel.folder
- property alias filter: selectionType.filtersString // FIXME: Currently only supports simple filters, "*.xxx".
- property int options // Not used.
-
- property bool selectDirectory: false
-
- // Not implemented.
- //property bool saveDialog: false;
- //property bool multiSelect: false;
-
- property bool singleClickNavigate: false
-
- HifiConstants { id: hifi }
-
- Component.onCompleted: {
- homeButton.destination = dir;
-
- if (selectDirectory) {
- d.currentSelectionIsFolder = true;
- d.currentSelectionPath = assetTableModel.folder;
- }
-
- assetTableView.forceActiveFocus();
- }
-
- Item {
- id: assetDialogItem
- anchors.fill: parent
- clip: true
-
- MouseArea {
- // Clear selection when click on internal unused area.
- anchors.fill: parent
- drag.target: root
- onClicked: {
- d.clearSelection();
- frame.forceActiveFocus();
- assetTableView.forceActiveFocus();
- }
- }
-
- Row {
- id: navControls
- anchors {
- top: parent.top
- topMargin: hifi.dimensions.contentMargin.y
- left: parent.left
- }
- spacing: hifi.dimensions.contentSpacing.x
-
- GlyphButton {
- id: upButton
- glyph: hifi.glyphs.levelUp
- width: height
- size: 30
- enabled: assetTableModel.parentFolder !== ""
- onClicked: d.navigateUp();
- }
-
- GlyphButton {
- id: homeButton
- property string destination: ""
- glyph: hifi.glyphs.home
- size: 28
- width: height
- enabled: destination !== ""
- //onClicked: d.navigateHome();
- onClicked: assetTableModel.folder = destination;
- }
- }
-
- ComboBox {
- id: pathSelector
- anchors {
- top: parent.top
- topMargin: hifi.dimensions.contentMargin.y
- left: navControls.right
- leftMargin: hifi.dimensions.contentSpacing.x
- right: parent.right
- }
- z: 10
-
- property string lastValidFolder: assetTableModel.folder
-
- function calculatePathChoices(folder) {
- var folders = folder.split("/"),
- choices = [],
- i, length;
-
- if (folders[folders.length - 1] === "") {
- folders.pop();
- }
-
- choices.push(folders[0]);
-
- for (i = 1, length = folders.length; i < length; i++) {
- choices.push(choices[i - 1] + "/" + folders[i]);
- }
-
- if (folders[0] === "") {
- choices[0] = "/";
- }
-
- choices.reverse();
-
- if (choices.length > 0) {
- pathSelector.model = choices;
- }
- }
-
- onLastValidFolderChanged: {
- var folder = lastValidFolder;
- calculatePathChoices(folder);
- }
-
- onCurrentTextChanged: {
- var folder = currentText;
-
- if (folder !== "/") {
- folder += "/";
- }
-
- if (folder !== assetTableModel.folder) {
- if (root.selectDirectory) {
- currentSelection.text = currentText;
- d.currentSelectionPath = currentText;
- }
- assetTableModel.folder = folder;
- assetTableView.forceActiveFocus();
- }
- }
- }
-
- QtObject {
- id: d
-
- property string currentSelectionPath
- property bool currentSelectionIsFolder
- property var tableViewConnection: Connections { target: assetTableView; onCurrentRowChanged: d.update(); }
-
- function update() {
- var row = assetTableView.currentRow;
-
- if (row === -1) {
- if (!root.selectDirectory) {
- currentSelection.text = "";
- currentSelectionIsFolder = false;
- }
- return;
- }
-
- var rowInfo = assetTableModel.get(row);
- currentSelectionPath = rowInfo.filePath;
- currentSelectionIsFolder = rowInfo.fileIsDir;
- if (root.selectDirectory || !currentSelectionIsFolder) {
- currentSelection.text = currentSelectionPath;
- } else {
- currentSelection.text = "";
- }
- }
-
- function navigateUp() {
- if (assetTableModel.parentFolder !== "") {
- assetTableModel.folder = assetTableModel.parentFolder;
- return true;
- }
- return false;
- }
-
- function navigateHome() {
- assetTableModel.folder = homeButton.destination;
- return true;
- }
-
- function clearSelection() {
- assetTableView.selection.clear();
- assetTableView.currentRow = -1;
- update();
- }
- }
-
- ListModel {
- id: assetTableModel
-
- property string folder
- property string parentFolder: ""
- readonly property string rootFolder: "/"
-
- onFolderChanged: {
- parentFolder = calculateParentFolder();
- update();
- }
-
- function calculateParentFolder() {
- if (folder !== "/") {
- return folder.slice(0, folder.slice(0, -1).lastIndexOf("/") + 1);
- }
- return "";
- }
-
- function isFolder(row) {
- if (row === -1) {
- return false;
- }
- return get(row).fileIsDir;
- }
-
- function onGetAllMappings(error, map) {
- var mappings,
- fileTypeFilter,
- index,
- path,
- fileName,
- fileType,
- fileIsDir,
- isValid,
- subDirectory,
- subDirectories = [],
- fileNameSort,
- rows = 0,
- lower,
- middle,
- upper,
- i,
- length;
-
- clear();
-
- if (error === "") {
- mappings = Object.keys(map);
- fileTypeFilter = filter.replace("*", "").toLowerCase();
-
- for (i = 0, length = mappings.length; i < length; i++) {
- index = mappings[i].lastIndexOf("/");
-
- path = mappings[i].slice(0, mappings[i].lastIndexOf("/") + 1);
- fileName = mappings[i].slice(path.length);
- fileType = fileName.slice(fileName.lastIndexOf("."));
- fileIsDir = false;
- isValid = false;
-
- if (fileType.toLowerCase() === fileTypeFilter) {
- if (path === folder) {
- isValid = !selectDirectory;
- } else if (path.length > folder.length) {
- subDirectory = path.slice(folder.length);
- index = subDirectory.indexOf("/");
- if (index === subDirectory.lastIndexOf("/")) {
- fileName = subDirectory.slice(0, index);
- if (subDirectories.indexOf(fileName) === -1) {
- fileIsDir = true;
- isValid = true;
- subDirectories.push(fileName);
- }
- }
- }
- }
-
- if (isValid) {
- fileNameSort = (fileIsDir ? "*" : "") + fileName.toLowerCase();
-
- lower = 0;
- upper = rows;
- while (lower < upper) {
- middle = Math.floor((lower + upper) / 2);
- var lessThan;
- if (fileNameSort < get(middle)["fileNameSort"]) {
- lessThan = true;
- upper = middle;
- } else {
- lessThan = false;
- lower = middle + 1;
- }
- }
-
- insert(lower, {
- fileName: fileName,
- filePath: path + (fileIsDir ? "" : fileName),
- fileIsDir: fileIsDir,
- fileNameSort: fileNameSort
- });
-
- rows++;
- }
- }
-
- } else {
- console.log("Error getting mappings from Asset Server");
- }
- }
-
- function update() {
- d.clearSelection();
- clear();
- Assets.getAllMappings(onGetAllMappings);
- }
- }
-
- Table {
- id: assetTableView
- colorScheme: hifi.colorSchemes.light
- anchors {
- top: navControls.bottom
- topMargin: hifi.dimensions.contentSpacing.y
- left: parent.left
- right: parent.right
- bottom: currentSelection.top
- bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
- }
-
- model: assetTableModel
-
- focus: true
-
- onClicked: {
- if (singleClickNavigate) {
- navigateToRow(row);
- }
- }
-
- onDoubleClicked: navigateToRow(row);
- Keys.onReturnPressed: navigateToCurrentRow();
- Keys.onEnterPressed: navigateToCurrentRow();
-
- itemDelegate: Item {
- clip: true
-
- FiraSansSemiBold {
- text: styleData.value
- elide: styleData.elideMode
- anchors {
- left: parent.left
- leftMargin: hifi.dimensions.tablePadding
- right: parent.right
- rightMargin: hifi.dimensions.tablePadding
- verticalCenter: parent.verticalCenter
- }
- size: hifi.fontSizes.tableText
- color: hifi.colors.baseGrayHighlight
- font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir)
- ? "Fira Sans SemiBold" : "Fira Sans"
- }
- }
-
- TableViewColumn {
- id: fileNameColumn
- role: "fileName"
- title: "Name"
- width: assetTableView.width
- movable: false
- resizable: false
- }
-
- function navigateToRow(row) {
- currentRow = row;
- navigateToCurrentRow();
- }
-
- function navigateToCurrentRow() {
- if (model.isFolder(currentRow)) {
- model.folder = model.get(currentRow).filePath;
- } else {
- okAction.trigger();
- }
- }
-
- Timer {
- id: prefixClearTimer
- interval: 1000
- repeat: false
- running: false
- onTriggered: assetTableView.prefix = "";
- }
-
- property string prefix: ""
-
- function addToPrefix(event) {
- if (!event.text || event.text === "") {
- return false;
- }
- var newPrefix = prefix + event.text.toLowerCase();
- var matchedIndex = -1;
- for (var i = 0; i < model.count; ++i) {
- var name = model.get(i).fileName.toLowerCase();
- if (0 === name.indexOf(newPrefix)) {
- matchedIndex = i;
- break;
- }
- }
-
- if (matchedIndex !== -1) {
- assetTableView.selection.clear();
- assetTableView.selection.select(matchedIndex);
- assetTableView.currentRow = matchedIndex;
- assetTableView.prefix = newPrefix;
- }
- prefixClearTimer.restart();
- return true;
- }
-
- Keys.onPressed: {
- switch (event.key) {
- case Qt.Key_Backspace:
- case Qt.Key_Tab:
- case Qt.Key_Backtab:
- event.accepted = false;
- break;
-
- default:
- if (addToPrefix(event)) {
- event.accepted = true
- } else {
- event.accepted = false;
- }
- break;
- }
- }
- }
-
- TextField {
- id: currentSelection
- label: selectDirectory ? "Directory:" : "File name:"
- anchors {
- left: parent.left
- right: selectionType.visible ? selectionType.left: parent.right
- rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
- bottom: buttonRow.top
- bottomMargin: hifi.dimensions.contentSpacing.y
- }
- readOnly: true
- activeFocusOnTab: !readOnly
- onActiveFocusChanged: if (activeFocus) { selectAll(); }
- onAccepted: okAction.trigger();
- }
-
- FileTypeSelection {
- id: selectionType
- anchors {
- top: currentSelection.top
- left: buttonRow.left
- right: parent.right
- }
- visible: !selectDirectory && filtersCount > 1
- KeyNavigation.left: assetTableView
- KeyNavigation.right: openButton
- }
-
- Action {
- id: okAction
- text: currentSelection.text && root.selectDirectory && assetTableView.currentRow === -1 ? "Choose" : "Open"
- enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
- onTriggered: {
- if (!root.selectDirectory && !d.currentSelectionIsFolder
- || root.selectDirectory && assetTableView.currentRow === -1) {
- selectedAsset(d.currentSelectionPath);
- root.destroy();
- } else {
- assetTableView.navigateToCurrentRow();
- }
- }
- }
-
- Action {
- id: cancelAction
- text: "Cancel"
- onTriggered: {
- canceled();
- root.destroy();
- }
- }
-
- Row {
- id: buttonRow
- anchors {
- right: parent.right
- bottom: parent.bottom
- }
- spacing: hifi.dimensions.contentSpacing.y
-
- Button {
- id: openButton
- color: hifi.buttons.blue
- action: okAction
- Keys.onReturnPressed: okAction.trigger()
- KeyNavigation.up: selectionType
- KeyNavigation.left: selectionType
- KeyNavigation.right: cancelButton
- }
-
- Button {
- id: cancelButton
- action: cancelAction
- KeyNavigation.up: selectionType
- KeyNavigation.left: openButton
- KeyNavigation.right: assetTableView.contentItem
- Keys.onReturnPressed: { canceled(); root.enabled = false }
- }
- }
- }
-
- Keys.onPressed: {
- switch (event.key) {
- case Qt.Key_Backspace:
- event.accepted = d.navigateUp();
- break;
-
- case Qt.Key_Home:
- event.accepted = d.navigateHome();
- break;
-
- }
- }
-}
diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml
index 89e1096a04..b2c334b674 100644
--- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml
+++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml
@@ -26,11 +26,9 @@ Preference {
preference.save();
}
- Item {
+ Row {
id: control
anchors {
- left: parent.left
- right: parent.right
bottom: parent.bottom
}
height: Math.max(spinnerLabel.height, spinner.controlHeight)
@@ -40,15 +38,14 @@ Preference {
text: root.label + ":"
colorScheme: hifi.colorSchemes.dark
anchors {
- left: parent.left
- right: spinner.left
- rightMargin: hifi.dimensions.labelPadding
verticalCenter: parent.verticalCenter
}
horizontalAlignment: Text.AlignRight
wrapMode: Text.Wrap
}
+ spacing: hifi.dimensions.labelPadding
+
SpinBox {
id: spinner
decimals: preference.decimals
@@ -56,7 +53,6 @@ Preference {
maximumValue: preference.max
width: 100
anchors {
- right: parent.right
verticalCenter: parent.verticalCenter
}
colorScheme: hifi.colorSchemes.dark
diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml
index 731acc7e5b..126e62fc30 100644
--- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml
+++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml
@@ -56,8 +56,8 @@ Preference {
id: slider
value: preference.value
width: 100
- minimumValue: MyAvatar.getDomainMinScale()
- maximumValue: MyAvatar.getDomainMaxScale()
+ minimumValue: preference.min
+ maximumValue: preference.max
stepSize: preference.step
onValueChanged: {
spinner.realValue = value
@@ -74,8 +74,8 @@ Preference {
id: spinner
decimals: preference.decimals
realValue: preference.value
- minimumValue: MyAvatar.getDomainMinScale()
- maximumValue: MyAvatar.getDomainMaxScale()
+ minimumValue: preference.min
+ maximumValue: preference.max
width: 100
onValueChanged: {
slider.value = realValue;
diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml
new file mode 100644
index 0000000000..0a2dcb951b
--- /dev/null
+++ b/interface/resources/qml/hifi/AvatarApp.qml
@@ -0,0 +1,840 @@
+import QtQuick 2.6
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import QtQml.Models 2.1
+import QtGraphicalEffects 1.0
+import "../controls-uit" as HifiControls
+import "../styles-uit"
+import "avatarapp"
+
+Rectangle {
+ id: root
+ width: 480
+ height: 706
+
+ property bool keyboardEnabled: true
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
+ HifiControls.Keyboard {
+ id: keyboard
+ z: 1000
+ raised: parent.keyboardEnabled && parent.keyboardRaised
+ numeric: parent.punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ }
+
+ color: style.colors.white
+ property string getAvatarsMethod: 'getAvatars'
+
+ signal sendToScript(var message);
+ function emitSendToScript(message) {
+ sendToScript(message);
+ }
+
+ ListModel { // the only purpose of this model is to convert JS object to ListElement
+ id: currentAvatarModel
+ dynamicRoles: true;
+ function makeAvatarEntry(avatarObject) {
+ clear();
+ append(avatarObject);
+ return get(count - 1);
+ }
+ }
+
+ property var jointNames;
+ property var currentAvatarSettings;
+
+ function fetchAvatarModelName(marketId, avatar) {
+ var xmlhttp = new XMLHttpRequest();
+ var url = "https://highfidelity.com/api/v1/marketplace/items/" + marketId;
+ xmlhttp.onreadystatechange = function() {
+ if (xmlhttp.readyState === XMLHttpRequest.DONE && xmlhttp.status === 200) {
+ try {
+ var marketResponse = JSON.parse(xmlhttp.responseText.trim())
+
+ if(marketResponse.status === 'success') {
+ avatar.modelName = marketResponse.data.title;
+ }
+ }
+ catch(err) {
+ console.error(err);
+ }
+ }
+ }
+ xmlhttp.open("GET", url, true);
+ xmlhttp.send();
+ }
+
+ function getAvatarModelName() {
+
+ if(currentAvatar === null) {
+ return '';
+ }
+ if(currentAvatar.modelName !== undefined) {
+ return currentAvatar.modelName;
+ } else {
+ var marketId = allAvatars.extractMarketId(currentAvatar.avatarUrl);
+ if(marketId !== '') {
+ fetchAvatarModelName(marketId, currentAvatar);
+ }
+ }
+
+ var avatarUrl = currentAvatar.avatarUrl;
+ var splitted = avatarUrl.split('/');
+
+ return splitted[splitted.length - 1];
+ }
+
+ property string avatarName: currentAvatar ? currentAvatar.name : ''
+ property string avatarUrl: currentAvatar ? currentAvatar.thumbnailUrl : null
+ property bool isAvatarInFavorites: currentAvatar ? allAvatars.findAvatar(currentAvatar.name) !== undefined : false
+ property int avatarWearablesCount: currentAvatar ? currentAvatar.wearables.count : 0
+ property var currentAvatar: null;
+ function setCurrentAvatar(avatar, bookmarkName) {
+ var currentAvatarObject = allAvatars.makeAvatarObject(avatar, bookmarkName);
+ currentAvatar = currentAvatarModel.makeAvatarEntry(currentAvatarObject);
+ }
+
+ property url externalAvatarThumbnailUrl: '../../images/avatarapp/guy-in-circle.svg'
+
+ function fromScript(message) {
+ if(message.method === 'initialize') {
+ jointNames = message.data.jointNames;
+ emitSendToScript({'method' : getAvatarsMethod});
+ } else if(message.method === 'wearableUpdated') {
+ adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties, message.updateUI);
+ } else if(message.method === 'wearablesUpdated') {
+ var wearablesModel = currentAvatar.wearables;
+ wearablesModel.clear();
+ message.wearables.forEach(function(wearable) {
+ wearablesModel.append(wearable);
+ });
+ adjustWearables.refresh(currentAvatar);
+ } else if(message.method === 'scaleChanged') {
+ currentAvatar.avatarScale = message.value;
+ updateCurrentAvatarInBookmarks(currentAvatar);
+ } else if(message.method === 'externalAvatarApplied') {
+ currentAvatar.avatarUrl = message.avatarURL;
+ currentAvatar.thumbnailUrl = allAvatars.makeThumbnailUrl(message.avatarURL);
+ currentAvatar.entry.avatarUrl = currentAvatar.avatarUrl;
+ currentAvatar.modelName = undefined;
+ updateCurrentAvatarInBookmarks(currentAvatar);
+ } else if(message.method === 'settingChanged') {
+ currentAvatarSettings[message.name] = message.value;
+ } else if(message.method === 'changeSettings') {
+ currentAvatarSettings = message.settings;
+ } else if(message.method === 'bookmarkLoaded') {
+ setCurrentAvatar(message.data.currentAvatar, message.data.name);
+ var avatarIndex = allAvatars.findAvatarIndex(currentAvatar.name);
+ allAvatars.move(avatarIndex, 0, 1);
+ view.setPage(0);
+ } else if(message.method === 'bookmarkAdded') {
+ var avatar = allAvatars.findAvatar(message.bookmarkName);
+ if(avatar !== undefined) {
+ var avatarObject = allAvatars.makeAvatarObject(message.bookmark, message.bookmarkName);
+ for(var prop in avatarObject) {
+ avatar[prop] = avatarObject[prop];
+ }
+ if(currentAvatar.name === message.bookmarkName) {
+ currentAvatar = currentAvatarModel.makeAvatarEntry(avatarObject);
+ }
+ } else {
+ allAvatars.addAvatarEntry(message.bookmark, message.bookmarkName);
+ }
+ updateCurrentAvatarInBookmarks(currentAvatar);
+ } else if(message.method === 'bookmarkDeleted') {
+ pageOfAvatars.isUpdating = true;
+
+ var index = pageOfAvatars.findAvatarIndex(message.name);
+ var absoluteIndex = view.currentPage * view.itemsPerPage + index
+
+ allAvatars.remove(absoluteIndex)
+ pageOfAvatars.remove(index);
+
+ var itemsOnPage = pageOfAvatars.count;
+ var newItemIndex = view.currentPage * view.itemsPerPage + itemsOnPage;
+
+ if(newItemIndex <= (allAvatars.count - 1)) {
+ pageOfAvatars.append(allAvatars.get(newItemIndex));
+ } else {
+ if(!pageOfAvatars.hasGetAvatars())
+ pageOfAvatars.appendGetAvatars();
+ }
+
+ pageOfAvatars.isUpdating = false;
+ } else if(message.method === getAvatarsMethod) {
+ var getAvatarsData = message.data;
+ allAvatars.populate(getAvatarsData.bookmarks);
+ setCurrentAvatar(getAvatarsData.currentAvatar, '');
+ displayNameInput.text = getAvatarsData.displayName;
+ currentAvatarSettings = getAvatarsData.currentAvatarSettings;
+
+ updateCurrentAvatarInBookmarks(currentAvatar);
+ } else if(message.method === 'updateAvatarInBookmarks') {
+ updateCurrentAvatarInBookmarks(currentAvatar);
+ } else if(message.method === 'selectAvatarEntity') {
+ adjustWearables.selectWearableByID(message.entityID);
+ }
+ }
+
+ function updateCurrentAvatarInBookmarks(avatar) {
+ var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(avatar);
+ if(bookmarkAvatarIndex === -1) {
+ avatar.name = '';
+ view.setPage(0);
+ } else {
+ var bookmarkAvatar = allAvatars.get(bookmarkAvatarIndex);
+ avatar.name = bookmarkAvatar.name;
+ view.selectAvatar(bookmarkAvatar);
+ }
+ }
+
+ property bool isInManageState: false
+
+ Component.onCompleted: {
+ }
+
+ AvatarAppStyle {
+ id: style
+ }
+
+ AvatarAppHeader {
+ id: header
+ z: 100
+
+ property string currentPage: "Avatar"
+ property bool mainPageVisible: !settings.visible && !adjustWearables.visible
+
+ Binding on currentPage {
+ when: settings.visible
+ value: "Avatar Settings"
+ }
+ Binding on currentPage {
+ when: adjustWearables.visible
+ value: "Adjust Wearables"
+ }
+ Binding on currentPage {
+ when: header.mainPageVisible
+ value: "Avatar"
+ }
+
+ pageTitle: currentPage
+ avatarIconVisible: mainPageVisible
+ settingsButtonVisible: mainPageVisible
+ onSettingsClicked: {
+ settings.open(currentAvatarSettings, currentAvatar.avatarScale);
+ }
+ }
+
+ Settings {
+ id: settings
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+
+ z: 3
+
+ onSaveClicked: function() {
+ var avatarSettings = {
+ dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
+ collisionsEnabled : settings.avatarCollisionsOn,
+ animGraphUrl : settings.avatarAnimationJSON,
+ collisionSoundUrl : settings.avatarCollisionSoundUrl
+ };
+
+ emitSendToScript({'method' : 'saveSettings', 'settings' : avatarSettings, 'avatarScale': settings.scaleValue})
+
+ close();
+ }
+ onCancelClicked: function() {
+ emitSendToScript({'method' : 'revertScale', 'avatarScale' : avatarScaleBackup});
+
+ close();
+ }
+
+ onScaleChanged: {
+ emitSendToScript({'method' : 'setScale', 'avatarScale' : scale})
+ }
+ }
+
+ AdjustWearables {
+ id: adjustWearables
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: header.bottom
+ anchors.bottom: parent.bottom
+ jointNames: root.jointNames
+ onWearableUpdated: {
+ emitSendToScript({'method' : 'adjustWearable', 'entityID' : id, 'wearableIndex' : index, 'properties' : properties})
+ }
+ onWearableDeleted: {
+ emitSendToScript({'method' : 'deleteWearable', 'entityID' : id, 'avatarName' : avatarName});
+ }
+ onAdjustWearablesOpened: {
+ emitSendToScript({'method' : 'adjustWearablesOpened', 'avatarName' : avatarName});
+ }
+ onAdjustWearablesClosed: {
+ emitSendToScript({'method' : 'adjustWearablesClosed', 'save' : status, 'avatarName' : avatarName});
+ }
+ onWearableSelected: {
+ emitSendToScript({'method' : 'selectWearable', 'entityID' : id});
+ }
+
+ z: 3
+ }
+
+ Rectangle {
+ id: mainBlock
+ anchors.left: parent.left
+ anchors.leftMargin: 30
+ anchors.right: parent.right
+ anchors.rightMargin: 30
+ anchors.top: header.bottom
+ anchors.bottom: favoritesBlock.top
+
+ // TextStyle1
+ RalewaySemiBold {
+ size: 24;
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: 34
+ }
+
+ // TextStyle1
+ RalewaySemiBold {
+ id: displayNameLabel
+ size: 24;
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: 25
+ text: 'Display Name'
+ }
+
+ InputField {
+ id: displayNameInput
+
+ font.family: "Fira Sans"
+ font.pixelSize: 15
+ anchors.left: displayNameLabel.right
+ anchors.leftMargin: 30
+ anchors.verticalCenter: displayNameLabel.verticalCenter
+ anchors.right: parent.right
+ width: 232
+
+ text: 'ThisIsDisplayName'
+
+ onEditingFinished: {
+ emitSendToScript({'method' : 'changeDisplayName', 'displayName' : text})
+ focus = false;
+ }
+ }
+
+ ShadowImage {
+ id: avatarImage
+ width: 134
+ height: 134
+ anchors.top: displayNameLabel.bottom
+ anchors.topMargin: 31
+ Binding on source {
+ when: avatarUrl !== ''
+ value: avatarUrl
+ }
+
+ visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error
+ fillMode: Image.PreserveAspectCrop
+ }
+
+ ShadowImage {
+ id: customAvatarImage
+ anchors.fill: avatarImage;
+ visible: avatarUrl === '' || avatarImage.status === Image.Error
+ source: externalAvatarThumbnailUrl
+ }
+
+ ShadowRectangle {
+ anchors.fill: avatarImage;
+ color: 'white'
+ visible: avatarImage.status === Image.Loading
+ radius: avatarImage.radius
+
+ dropShadowRadius: avatarImage.dropShadowRadius;
+ dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
+ dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
+
+ Spinner {
+ id: spinner
+ visible: parent.visible
+ anchors.fill: parent;
+ }
+ }
+
+ AvatarWearablesIndicator {
+ anchors.right: avatarImage.right
+ anchors.bottom: avatarImage.bottom
+ anchors.rightMargin: -radius
+ anchors.bottomMargin: 6.08
+ wearablesCount: avatarWearablesCount
+ visible: avatarWearablesCount !== 0
+ }
+
+ RowLayout {
+ id: star
+ anchors.top: avatarImage.top
+ anchors.topMargin: 11
+ anchors.left: avatarImage.right
+ anchors.leftMargin: 30.5
+ anchors.right: parent.right
+
+ spacing: 12.3
+
+ Image {
+ width: 21.2
+ height: 19.3
+ source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg'
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ // TextStyle5
+ FiraSansSemiBold {
+ size: 22;
+ Layout.fillWidth: true
+ text: isAvatarInFavorites ? avatarName : "Add to Favorites"
+ elide: Qt.ElideRight
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ MouseArea {
+ enabled: !isAvatarInFavorites
+ anchors.fill: star
+ onClicked: {
+ createFavorite.onSaveClicked = function() {
+ var entry = currentAvatar.entry;
+
+ var wearables = [];
+ for(var i = 0; i < currentAvatar.wearables.count; ++i) {
+ wearables.push(currentAvatar.wearables.get(i));
+ }
+
+ entry.avatarEntites = wearables;
+ currentAvatar.name = createFavorite.favoriteNameText;
+
+ emitSendToScript({'method': 'addAvatar', 'name' : currentAvatar.name});
+ createFavorite.close();
+ }
+
+ var avatarThumbnail = (avatarUrl === '' || avatarImage.status === Image.Error) ?
+ externalAvatarThumbnailUrl : avatarUrl;
+
+ createFavorite.open(root.currentAvatar.wearables.count, avatarThumbnail);
+ }
+ }
+
+ // TextStyle3
+ RalewayRegular {
+ id: avatarNameLabel
+ size: 22;
+ text: getAvatarModelName();
+ elide: Qt.ElideRight
+
+ anchors.right: linkLabel.left
+ anchors.left: avatarImage.right
+ anchors.leftMargin: 30
+ anchors.top: star.bottom
+ anchors.topMargin: 11
+ property bool hasMarketId: currentAvatar && allAvatars.extractMarketId(currentAvatar.avatarUrl) !== '';
+
+ MouseArea {
+ enabled: avatarNameLabel.hasMarketId
+ anchors.fill: parent;
+ onClicked: emitSendToScript({'method' : 'navigate', 'url' : allAvatars.makeMarketItemUrl(currentAvatar.avatarUrl)})
+ }
+ color: hasMarketId ? style.colors.blueHighlight : 'black'
+ }
+
+ // TextStyle3
+ RalewayRegular {
+ id: wearablesLabel
+ size: 22;
+ anchors.left: avatarImage.right
+ anchors.leftMargin: 30
+ anchors.top: avatarNameLabel.bottom
+ anchors.topMargin: 16
+ color: 'black'
+ text: 'Wearables'
+ }
+
+ SquareLabel {
+ id: linkLabel
+ anchors.right: parent.right
+ anchors.verticalCenter: avatarNameLabel.verticalCenter
+ glyphText: "."
+ glyphSize: 22
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ popup.showSpecifyAvatarUrl(function() {
+ var url = popup.inputText.text;
+ emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url})
+ }, function(link) {
+ Qt.openUrlExternally(link);
+ });
+ }
+ }
+ }
+
+ SquareLabel {
+ anchors.right: parent.right
+ anchors.verticalCenter: wearablesLabel.verticalCenter
+ glyphText: "\ue02e"
+
+ visible: avatarWearablesCount !== 0
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ adjustWearables.open(currentAvatar);
+ }
+ }
+ }
+
+ // TextStyle3
+ RalewayRegular {
+ size: 22;
+ anchors.right: parent.right
+ anchors.verticalCenter: wearablesLabel.verticalCenter
+ font.underline: true
+ text: "Add"
+ color: 'black'
+ visible: avatarWearablesCount === 0
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ popup.showGetWearables(function() {
+ emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'})
+ }, function(link) {
+ emitSendToScript({'method' : 'navigate', 'url' : link})
+ });
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: favoritesBlock
+ height: 407
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ color: style.colors.lightGrayBackground
+
+ // TextStyle1
+ RalewaySemiBold {
+ id: favoritesLabel
+ size: 24;
+ anchors.top: parent.top
+ anchors.topMargin: 15
+ anchors.left: parent.left
+ anchors.leftMargin: 30
+ text: "Favorites"
+ }
+
+ // TextStyle8
+ RalewaySemiBold {
+ id: manageLabel
+ color: style.colors.blueHighlight
+ size: 20;
+ anchors.top: parent.top
+ anchors.topMargin: 20
+ anchors.right: parent.right
+ anchors.rightMargin: 30
+ text: isInManageState ? "Back" : "Manage"
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ isInManageState = isInManageState ? false : true;
+ }
+ }
+ }
+
+ Item {
+ anchors.left: parent.left
+ anchors.leftMargin: 30
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+
+ anchors.top: favoritesLabel.bottom
+ anchors.topMargin: 20
+ anchors.bottom: parent.bottom
+
+ GridView {
+ id: view
+ anchors.fill: parent
+ interactive: false;
+ currentIndex: currentAvatarIndexInBookmarksPage();
+
+ function currentAvatarIndexInBookmarksPage() {
+ return (currentAvatar && currentAvatar.name !== '' && !pageOfAvatars.isUpdating) ? pageOfAvatars.findAvatarIndex(currentAvatar.name) : -1;
+ }
+
+ property int horizontalSpacing: 18
+ property int verticalSpacing: 44
+ property int thumbnailWidth: 92
+ property int thumbnailHeight: 92
+
+ function selectAvatar(avatar) {
+ emitSendToScript({'method' : 'selectAvatar', 'name' : avatar.name})
+ }
+
+ function deleteAvatar(avatar) {
+ emitSendToScript({'method' : 'deleteAvatar', 'name' : avatar.name})
+ }
+
+ AvatarsModel {
+ id: allAvatars
+ }
+
+ property int itemsPerPage: 8
+ property int totalPages: Math.ceil((allAvatars.count + 1) / itemsPerPage)
+ property int currentPage: 0;
+ onCurrentPageChanged: {
+ currentIndex = Qt.binding(currentAvatarIndexInBookmarksPage);
+ }
+
+ property bool hasNext: currentPage < (totalPages - 1)
+ property bool hasPrev: currentPage > 0
+
+ function setPage(pageIndex) {
+ pageOfAvatars.isUpdating = true;
+ pageOfAvatars.clear();
+ var start = pageIndex * itemsPerPage;
+ var end = Math.min(start + itemsPerPage, allAvatars.count);
+
+ for(var itemIndex = 0; start < end; ++start, ++itemIndex) {
+ var avatarItem = allAvatars.get(start)
+ pageOfAvatars.append(avatarItem);
+ }
+
+ if(pageOfAvatars.count !== itemsPerPage)
+ pageOfAvatars.appendGetAvatars();
+
+ currentPage = pageIndex;
+ pageOfAvatars.isUpdating = false;
+ }
+
+ model: AvatarsModel {
+ id: pageOfAvatars
+
+ property bool isUpdating: false;
+ property var getMoreAvatarsEntry: {'thumbnailUrl' : '', 'name' : '', 'getMoreAvatars' : true}
+
+ function appendGetAvatars() {
+ append(getMoreAvatarsEntry);
+ }
+
+ function hasGetAvatars() {
+ return count != 0 && get(count - 1).getMoreAvatars
+ }
+
+ function removeGetAvatars() {
+ if(hasGetAvatars()) {
+ remove(count - 1)
+ }
+ }
+ }
+
+ flow: GridView.FlowLeftToRight
+
+ cellHeight: thumbnailHeight + verticalSpacing
+ cellWidth: thumbnailWidth + horizontalSpacing
+
+ delegate: Item {
+ id: delegateRoot
+ height: GridView.view.cellHeight
+ width: GridView.view.cellWidth
+
+ Item {
+ id: container
+ width: 92
+ height: 92
+
+ Behavior on y {
+ NumberAnimation {
+ duration: 100
+ }
+ }
+
+ states: [
+ State {
+ name: "hovered"
+ when: favoriteAvatarMouseArea.containsMouse;
+ PropertyChanges { target: favoriteAvatarMouseArea; anchors.bottomMargin: -5 }
+ PropertyChanges { target: container; y: -5 }
+ PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 }
+ PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 }
+ }
+ ]
+
+ property bool highlighted: delegateRoot.GridView.isCurrentItem
+
+ AvatarThumbnail {
+ id: favoriteAvatarImage
+ externalAvatarThumbnailUrl: root.externalAvatarThumbnailUrl
+ avatarUrl: thumbnailUrl
+ border.color: container.highlighted ? style.colors.blueHighlight : 'transparent'
+ border.width: container.highlighted ? 4 : 0
+ wearablesCount: {
+ return !getMoreAvatars ? wearables.count : 0
+ }
+
+ visible: !getMoreAvatars
+
+ MouseArea {
+ id: favoriteAvatarMouseArea
+ anchors.fill: parent
+ anchors.margins: 0
+ enabled: !container.highlighted
+ hoverEnabled: enabled
+
+ onClicked: {
+ if(isInManageState) {
+ var currentItem = delegateRoot.GridView.view.model.get(index);
+ popup.showDeleteFavorite(currentItem.name, function() {
+ view.deleteAvatar(currentItem);
+ });
+ } else {
+ if(delegateRoot.GridView.view.currentIndex !== index) {
+ var currentItem = delegateRoot.GridView.view.model.get(index);
+ popup.showLoadFavorite(currentItem.name, function() {
+ view.selectAvatar(currentItem);
+ });
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.fill: favoriteAvatarImage
+ color: '#AFAFAF'
+ opacity: 0.4
+ radius: 5
+ visible: isInManageState && !container.highlighted && !getMoreAvatars
+ }
+
+ HiFiGlyphs {
+ anchors.fill: parent
+ text: "{"
+ visible: isInManageState && !container.highlighted && !getMoreAvatars
+ horizontalAlignment: Text.AlignHCenter
+ size: 56
+ }
+
+ ShadowRectangle {
+ width: 92
+ height: 92
+ radius: 5
+ color: style.colors.blueHighlight
+ visible: getMoreAvatars && !isInManageState
+
+ HiFiGlyphs {
+ anchors.centerIn: parent
+
+ color: 'white'
+ size: 60
+ text: "K"
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ popup.showBuyAvatars(function() {
+ emitSendToScript({'method' : 'navigate', 'url' : 'hifi://BodyMart'})
+ }, function(link) {
+ emitSendToScript({'method' : 'navigate', 'url' : link})
+ });
+ }
+ }
+ }
+ }
+
+ // TextStyle7
+ FiraSansRegular {
+ id: text
+ size: 18;
+ lineHeightMode: Text.FixedHeight
+ lineHeight: 16.9;
+ width: view.thumbnailWidth
+ height: view.verticalSpacing
+ elide: Qt.ElideRight
+ anchors.top: container.bottom
+ anchors.topMargin: 8
+ anchors.horizontalCenter: container.horizontalCenter
+ verticalAlignment: Text.AlignTop
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WrapAtWordBoundaryOrAnywhere
+ text: getMoreAvatars ? 'Get More Avatars' : name
+ visible: !getMoreAvatars || !isInManageState
+ }
+ }
+ }
+
+ }
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Rectangle {
+ width: 40
+ height: 40
+ color: 'transparent'
+
+ PageIndicator {
+ x: 1
+ hasNext: view.hasNext
+ hasPrev: view.hasPrev
+ onClicked: view.setPage(view.currentPage - 1)
+ }
+ }
+
+ spacing: 0
+
+ Rectangle {
+ width: 40
+ height: 40
+ color: 'transparent'
+
+ PageIndicator {
+ x: -1
+ isPrevious: false
+ hasNext: view.hasNext
+ hasPrev: view.hasPrev
+ onClicked: view.setPage(view.currentPage + 1)
+ }
+ }
+
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 20
+ }
+ }
+
+ MessageBoxes {
+ id: popup
+ }
+
+ CreateFavoriteDialog {
+ avatars: allAvatars
+ id: createFavorite
+ }
+}
diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml
index 5f2dfea8c7..785b586dd2 100644
--- a/interface/resources/qml/hifi/Feed.qml
+++ b/interface/resources/qml/hifi/Feed.qml
@@ -53,7 +53,7 @@ Column {
'protocol=' + encodeURIComponent(Window.protocolSignature())
];
endpoint: '/api/v1/user_stories?' + options.join('&');
- itemsPerPage: 3;
+ itemsPerPage: 4;
processPage: function (data) {
return data.user_stories.map(makeModelData);
};
@@ -106,7 +106,6 @@ Column {
highlightMoveDuration: -1;
highlightMoveVelocity: -1;
currentIndex: -1;
- onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } }
spacing: 12;
width: parent.width;
diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index 8dcb76442b..6884d2e1f6 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -61,7 +61,7 @@ Rectangle {
'username';
}
sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder;
- itemsPerPage: 9;
+ itemsPerPage: 10;
listView: connectionsTable;
processPage: function (data) {
return data.users.map(function (user) {
@@ -765,7 +765,7 @@ Rectangle {
TableViewColumn {
id: connectionsUserNameHeader;
role: "userName";
- title: connectionsTable.rowCount + (connectionsTable.rowCount === 1 ? " NAME" : " NAMES");
+ title: connectionsUserModel.totalEntries + (connectionsUserModel.totalEntries === 1 ? " NAME" : " NAMES");
width: connectionsNameCardWidth;
movable: false;
resizable: false;
@@ -786,14 +786,6 @@ Rectangle {
}
model: connectionsUserModel;
- Connections {
- target: connectionsTable.flickableItem;
- onAtYEndChanged: {
- if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) {
- connectionsUserModel.getNextPage();
- }
- }
- }
// This Rectangle refers to each Row in the connectionsTable.
rowDelegate: Rectangle {
@@ -1108,7 +1100,7 @@ Rectangle {
function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml
var data = optionalData || nearbyUserModelData, length = data.length;
for (var i = 0; i < length; i++) {
- if (data[i].sessionId === sessionId) {
+ if (data[i].sessionId === sessionId.toString()) {
return i;
}
}
@@ -1120,7 +1112,7 @@ Rectangle {
var data = message.params;
var index = -1;
iAmAdmin = Users.canKick;
- index = findNearbySessionIndex('', data);
+ index = findNearbySessionIndex("", data);
if (index !== -1) {
myData = data[index];
data.splice(index, 1);
@@ -1197,8 +1189,8 @@ Rectangle {
for (var userId in message.params) {
var audioLevel = message.params[userId][0];
var avgAudioLevel = message.params[userId][1];
- // If the userId is 0, we're updating "myData".
- if (userId == 0) {
+ // If the userId is "", we're updating "myData".
+ if (userId === "") {
myData.audioLevel = audioLevel;
myCard.audioLevel = audioLevel; // Defensive programming
myData.avgAudioLevel = avgAudioLevel;
diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index ba50b7f238..cc1ba49984 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -129,12 +129,10 @@ Rectangle {
id: stereoMic
spacing: muteMic.spacing;
text: qsTr("Enable stereo input");
- checked: AudioScriptingInterface.isStereoInput();
+ checked: AudioScriptingInterface.isStereoInput;
onClicked: {
- var success = AudioScriptingInterface.setStereoInput(checked);
- if (!success) {
- checked = !checked;
- }
+ AudioScriptingInterface.isStereoInput = checked;
+ checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
}
}
}
diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml
new file mode 100644
index 0000000000..a501185853
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml
@@ -0,0 +1,359 @@
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+import "../../controls" as HifiControls
+
+Rectangle {
+ id: root;
+ visible: false;
+ width: 480
+ height: 706
+ color: 'white'
+
+ signal wearableUpdated(var id, int index, var properties);
+ signal wearableSelected(var id);
+ signal wearableDeleted(string avatarName, var id);
+
+ signal adjustWearablesOpened(var avatarName);
+ signal adjustWearablesClosed(bool status, var avatarName);
+
+ property bool modified: false;
+ Component.onCompleted: {
+ modified = false;
+ }
+
+ property var jointNames;
+ property string avatarName: ''
+ property var wearablesModel;
+
+ function open(avatar) {
+ adjustWearablesOpened(avatar.name);
+
+ visible = true;
+ avatarName = avatar.name;
+ wearablesModel = avatar.wearables;
+ refresh(avatar);
+ }
+
+ function refresh(avatar) {
+ wearablesCombobox.model.clear();
+ for(var i = 0; i < avatar.wearables.count; ++i) {
+ var wearable = avatar.wearables.get(i).properties;
+ for(var j = (wearable.modelURL.length - 1); j >= 0; --j) {
+ if(wearable.modelURL[j] === '/') {
+ wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]);
+ break;
+ }
+ }
+ wearablesCombobox.model.append(wearable);
+ }
+
+ wearablesCombobox.currentIndex = 0;
+ }
+
+ function refreshWearable(wearableID, wearableIndex, properties, updateUI) {
+ if(wearableIndex === -1) {
+ wearableIndex = wearablesCombobox.model.findIndexById(wearableID);
+ }
+
+ var wearable = wearablesCombobox.model.get(wearableIndex);
+
+ if(!wearable) {
+ return;
+ }
+
+ var wearableModelItemProperties = wearablesModel.get(wearableIndex).properties;
+
+ for(var prop in properties) {
+ wearable[prop] = properties[prop];
+ wearableModelItemProperties[prop] = wearable[prop];
+
+ if(updateUI) {
+ if(prop === 'localPosition') {
+ position.set(wearable[prop]);
+ } else if(prop === 'localRotationAngles') {
+ rotation.set(wearable[prop]);
+ } else if(prop === 'dimensions') {
+ scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
+ }
+ }
+ }
+
+ wearablesModel.setProperty(wearableIndex, 'properties', wearableModelItemProperties);
+ }
+
+ function getCurrentWearable() {
+ return wearablesCombobox.model.get(wearablesCombobox.currentIndex)
+ }
+
+ function selectWearableByID(entityID) {
+ for(var i = 0; i < wearablesCombobox.model.count; ++i) {
+ var wearable = wearablesCombobox.model.get(i);
+ if(wearable.id === entityID) {
+ wearablesCombobox.currentIndex = i;
+ break;
+ }
+ }
+ }
+
+ function close(status) {
+ visible = false;
+ adjustWearablesClosed(status, avatarName);
+ }
+
+ HifiConstants { id: hifi }
+
+ // This object is always used in a popup.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ hoverEnabled: true;
+ }
+
+ Column {
+ anchors.top: parent.top
+ anchors.topMargin: 15
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ spacing: 20
+ width: parent.width - 30 * 2
+
+ HifiControlsUit.ComboBox {
+ id: wearablesCombobox
+ anchors.left: parent.left
+ anchors.right: parent.right
+ comboBox.textRole: "text"
+
+ model: ListModel {
+ function findIndexById(id) {
+
+ for(var i = 0; i < count; ++i) {
+ var wearable = get(i);
+ if(wearable.id === id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+ }
+
+ comboBox.onCurrentIndexChanged: {
+ var currentWearable = getCurrentWearable();
+
+ if(currentWearable) {
+ position.set(currentWearable.localPosition);
+ rotation.set(currentWearable.localRotationAngles);
+ scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x)
+
+ wearableSelected(currentWearable.id);
+ }
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: 5
+
+ Row {
+ spacing: 20
+
+ // TextStyle5
+ FiraSansSemiBold {
+ id: positionLabel
+ size: 22;
+ text: "Position"
+ }
+
+ // TextStyle7
+ FiraSansRegular {
+ size: 18;
+ lineHeightMode: Text.FixedHeight
+ lineHeight: 16.9;
+ text: "m"
+ anchors.verticalCenter: positionLabel.verticalCenter
+ }
+ }
+
+ Vector3 {
+ id: position
+ backgroundColor: "lightgray"
+
+ function set(localPosition) {
+ notify = false;
+ xvalue = localPosition.x
+ yvalue = localPosition.y
+ zvalue = localPosition.z
+ notify = true;
+ }
+
+ function notifyPositionChanged() {
+ modified = true;
+ var properties = {
+ localPosition: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
+ };
+
+ wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
+ }
+
+ property bool notify: false;
+
+ onXvalueChanged: if(notify) notifyPositionChanged();
+ onYvalueChanged: if(notify) notifyPositionChanged();
+ onZvalueChanged: if(notify) notifyPositionChanged();
+
+ decimals: 2
+ realFrom: -10
+ realTo: 10
+ realStepSize: 0.01
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: 5
+
+ Row {
+ spacing: 20
+
+ // TextStyle5
+ FiraSansSemiBold {
+ id: rotationLabel
+ size: 22;
+ text: "Rotation"
+ }
+
+ // TextStyle7
+ FiraSansRegular {
+ size: 18;
+ lineHeightMode: Text.FixedHeight
+ lineHeight: 16.9;
+ text: "deg"
+ anchors.verticalCenter: rotationLabel.verticalCenter
+ }
+ }
+
+ Vector3 {
+ id: rotation
+ backgroundColor: "lightgray"
+
+ function set(localRotationAngles) {
+ notify = false;
+ xvalue = localRotationAngles.x
+ yvalue = localRotationAngles.y
+ zvalue = localRotationAngles.z
+ notify = true;
+ }
+
+ function notifyRotationChanged() {
+ modified = true;
+ var properties = {
+ localRotationAngles: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue }
+ };
+
+ wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
+ }
+
+ property bool notify: false;
+
+ onXvalueChanged: if(notify) notifyRotationChanged();
+ onYvalueChanged: if(notify) notifyRotationChanged();
+ onZvalueChanged: if(notify) notifyRotationChanged();
+
+ decimals: 0
+ realFrom: -180
+ realTo: 180
+ realStepSize: 1
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: 5
+
+ // TextStyle5
+ FiraSansSemiBold {
+ size: 22;
+ text: "Scale"
+ }
+
+ Item {
+ width: parent.width
+ height: childrenRect.height
+
+ HifiControlsUit.SpinBox {
+ id: scalespinner
+ decimals: 2
+ realStepSize: 0.1
+ realFrom: 0.1
+ realTo: 3.0
+ realValue: 1.0
+ backgroundColor: "lightgray"
+ width: position.spinboxWidth
+ colorScheme: hifi.colorSchemes.light
+
+ property bool notify: false;
+ onValueChanged: if(notify) notifyScaleChanged();
+
+ function set(value) {
+ notify = false;
+ realValue = value
+ notify = true;
+ }
+
+ function notifyScaleChanged() {
+ modified = true;
+ var currentWearable = getCurrentWearable();
+ var naturalDimensions = currentWearable.naturalDimensions;
+
+ var properties = {
+ dimensions: {
+ 'x' : realValue * naturalDimensions.x,
+ 'y' : realValue * naturalDimensions.y,
+ 'z' : realValue * naturalDimensions.z
+ }
+ };
+
+ wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties);
+ }
+ }
+
+ HifiControlsUit.Button {
+ fontSize: 18
+ height: 40
+ anchors.right: parent.right
+ color: hifi.buttons.red;
+ colorScheme: hifi.colorSchemes.dark;
+ text: "TAKE IT OFF"
+ onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id);
+ enabled: wearablesCombobox.model.count !== 0
+ anchors.verticalCenter: scalespinner.verticalCenter
+ }
+ }
+
+ }
+ }
+
+ DialogButtons {
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 30
+ anchors.left: parent.left
+ anchors.leftMargin: 30
+ anchors.right: parent.right
+ anchors.rightMargin: 30
+
+ yesText: "SAVE"
+ noText: "CANCEL"
+
+ onYesClicked: function() {
+ root.close(true);
+ }
+
+ onNoClicked: function() {
+ root.close(false);
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml
new file mode 100644
index 0000000000..9d9db010fb
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml
@@ -0,0 +1,58 @@
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import "../../styles-uit"
+
+ShadowRectangle {
+ id: header
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 60
+
+ property alias pageTitle: title.text
+ property alias avatarIconVisible: avatarIcon.visible
+ property alias settingsButtonVisible: settingsButton.visible
+
+ signal settingsClicked;
+
+ AvatarAppStyle {
+ id: style
+ }
+
+ color: style.colors.lightGrayBackground
+
+ HiFiGlyphs {
+ id: avatarIcon
+ anchors.left: parent.left
+ anchors.leftMargin: 23
+ anchors.verticalCenter: header.verticalCenter
+
+ size: 38
+ text: "<"
+ }
+
+ // TextStyle6
+ RalewaySemiBold {
+ id: title
+ size: 22;
+ anchors.left: avatarIcon.visible ? avatarIcon.right : avatarIcon.left
+ anchors.leftMargin: 4
+ anchors.verticalCenter: avatarIcon.verticalCenter
+ text: 'Avatar'
+ }
+
+ HiFiGlyphs {
+ id: settingsButton
+ anchors.right: parent.right
+ anchors.rightMargin: 30
+ anchors.verticalCenter: avatarIcon.verticalCenter
+ text: "&"
+
+ MouseArea {
+ id: settingsMouseArea
+ anchors.fill: parent
+ onClicked: {
+ settingsClicked();
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml
new file mode 100644
index 0000000000..f66c7121cb
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml
@@ -0,0 +1,29 @@
+//
+// HiFiConstants.qml
+//
+// Created by Alexander Ivash on 17 Apr 2018
+// Copyright 2018 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Window 2.2
+import "../../styles-uit"
+
+QtObject {
+ readonly property QtObject colors: QtObject {
+ readonly property color lightGrayBackground: "#f2f2f2"
+ readonly property color black: "#000000"
+ readonly property color white: "#ffffff"
+ readonly property color blueHighlight: "#00b4ef"
+ readonly property color inputFieldBackground: "#d4d4d4"
+ readonly property color yellowishOrange: "#ffb017"
+ readonly property color blueAccent: "#0093c5"
+ readonly property color greenHighlight: "#1fc6a6"
+ readonly property color lightGray: "#afafaf"
+ readonly property color redHighlight: "#ea4c5f"
+ readonly property color orangeAccent: "#ff6309"
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml b/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml
new file mode 100644
index 0000000000..970d132ba6
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml
@@ -0,0 +1,73 @@
+import QtQuick 2.9
+import QtGraphicalEffects 1.0
+
+Item {
+ width: 92
+ height: 92
+ property alias wearableIndicator: indicator
+
+ property int wearablesCount: 0
+ onWearablesCountChanged: {
+ console.debug('AvatarThumbnail: wearablesCount = ', wearablesCount)
+ }
+
+ property alias dropShadowRadius: avatarImage.dropShadowRadius
+ property alias dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
+ property alias dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
+
+ property url externalAvatarThumbnailUrl;
+ property var avatarUrl;
+ property alias border: avatarImage.border
+
+ ShadowImage {
+ id: avatarImage
+ anchors.fill: parent
+ radius: 5
+ fillMode: Image.PreserveAspectCrop
+
+ Binding on source {
+ when: avatarUrl !== ''
+ value: avatarUrl
+ }
+ onSourceChanged: {
+ console.debug('avatarImage: source = ', source);
+ }
+
+ visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error
+ }
+
+ ShadowImage {
+ id: customAvatarImage
+ anchors.fill: avatarImage;
+ visible: avatarUrl === '' || avatarImage.status === Image.Error
+ source: externalAvatarThumbnailUrl
+ }
+
+ ShadowRectangle {
+ anchors.fill: parent;
+ color: 'white'
+ visible: avatarImage.status === Image.Loading
+ radius: avatarImage.radius
+ border.width: avatarImage.border.width
+ border.color: avatarImage.border.color
+
+ dropShadowRadius: avatarImage.dropShadowRadius;
+ dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset
+ dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset
+
+ Spinner {
+ id: spinner
+ visible: parent.visible
+ anchors.fill: parent;
+ }
+ }
+
+ AvatarWearablesIndicator {
+ id: indicator
+ anchors.left: avatarImage.left
+ anchors.bottom: avatarImage.bottom
+ anchors.leftMargin: 57
+ wearablesCount: parent.wearablesCount
+ visible: parent.wearablesCount !== 0
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml
new file mode 100644
index 0000000000..cb73e9fe71
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml
@@ -0,0 +1,46 @@
+import QtQuick 2.9
+import "../../controls-uit"
+import "../../styles-uit"
+
+ShadowRectangle {
+ property int wearablesCount: 0
+
+ dropShadowRadius: 4
+ dropShadowHorizontalOffset: 0
+ dropShadowVerticalOffset: 0
+
+ width: 46.5
+ height: 46.5
+ radius: width / 2
+
+ AvatarAppStyle {
+ id: style
+ }
+
+ color: style.colors.greenHighlight
+
+ HiFiGlyphs {
+ width: 26.5
+ height: 13.8
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ horizontalAlignment: Text.AlignHCenter
+ text: "\ue02e"
+ }
+
+ Item {
+ width: 46.57
+ height: 23
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 2.76
+
+ // TextStyle2
+ RalewayBold {
+ size: 15;
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: wearablesCount
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml
new file mode 100644
index 0000000000..931a041747
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml
@@ -0,0 +1,228 @@
+import QtQuick 2.9
+
+ListModel {
+ id: model
+ function extractMarketId(avatarUrl) {
+
+ var guidRegexp = '([A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12})';
+
+ var regexp = new RegExp(guidRegexp,["i"]);
+ var match = regexp.exec(avatarUrl);
+ if (match !== null) {
+ return match[1];
+ }
+
+ return '';
+ }
+
+ function makeMarketItemUrl(avatarUrl) {
+ var marketItemUrl = "https://highfidelity.com/marketplace/items/%marketId%"
+ .split('%marketId%').join(extractMarketId(avatarUrl));
+
+ return marketItemUrl;
+ }
+
+ function makeThumbnailUrl(avatarUrl) {
+ var marketId = extractMarketId(avatarUrl);
+ if(marketId === '')
+ return '';
+
+ var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg"
+ .split('%marketId%').join(marketId);
+
+ return avatarThumbnailUrl;
+ }
+
+ function makeAvatarObject(avatar, avatarName) {
+ var avatarThumbnailUrl = makeThumbnailUrl(avatar.avatarUrl);
+
+ return {
+ 'name' : avatarName,
+ 'avatarScale' : avatar.avatarScale,
+ 'thumbnailUrl' : avatarThumbnailUrl,
+ 'avatarUrl' : avatar.avatarUrl,
+ 'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [],
+ 'attachments' : avatar.attachments ? avatar.attachments : [],
+ 'entry' : avatar,
+ 'getMoreAvatars' : false
+ };
+
+ }
+
+ function addAvatarEntry(avatar, avatarName) {
+ var avatarEntry = makeAvatarObject(avatar, avatarName);
+ append(avatarEntry);
+
+ return allAvatars.count - 1;
+ }
+
+ function populate(bookmarks) {
+ clear();
+ for(var avatarName in bookmarks) {
+ var avatar = bookmarks[avatarName];
+ var avatarEntry = makeAvatarObject(avatar, avatarName);
+
+ append(avatarEntry);
+ }
+ }
+
+ function arraysAreEqual(a1, a2, comparer) {
+ if(Array.isArray(a1) && Array.isArray(a2)) {
+ if(a1.length !== a2.length) {
+ return false;
+ }
+
+ for(var i = 0; i < a1.length; ++i) {
+ if(!comparer(a1[i], a2[i])) {
+ return false;
+ }
+ }
+ } else if(Array.isArray(a1)) {
+ return a1.length === 0;
+ } else if(Array.isArray(a2)) {
+ return a2.length === 0;
+ }
+
+ return true;
+ }
+
+ function modelsAreEqual(m1, m2, comparer) {
+ if(m1.count !== m2.count) {
+ return false;
+ }
+
+ for(var i = 0; i < m1.count; ++i) {
+ var e1 = m1.get(i);
+
+ var allDifferent = true;
+
+ // it turns out order of wearables can randomly change so make position-independent comparison here
+ for(var j = 0; j < m2.count; ++j) {
+ var e2 = m2.get(j);
+
+ if(comparer(e1, e2)) {
+ allDifferent = false;
+ break;
+ }
+ }
+
+ if(allDifferent) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function compareNumericObjects(o1, o2) {
+ if(o1 === undefined && o2 !== undefined)
+ return false;
+ if(o1 !== undefined && o2 === undefined)
+ return false;
+
+ for(var prop in o1) {
+ if(o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) {
+ var v1 = o1[prop];
+ var v2 = o2[prop];
+
+
+ if(v1 !== v2 && Math.round(v1 * 500) != Math.round(v2 * 500)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function compareObjects(o1, o2, props, arrayProp) {
+ for(var i = 0; i < props.length; ++i) {
+ var prop = props[i];
+ var propertyName = prop.propertyName;
+ var comparer = prop.comparer;
+
+ var o1Value = arrayProp ? o1[arrayProp][propertyName] : o1[propertyName];
+ var o2Value = arrayProp ? o2[arrayProp][propertyName] : o2[propertyName];
+
+ if(comparer) {
+ if(comparer(o1Value, o2Value) === false) {
+ return false;
+ }
+ } else {
+ if(JSON.stringify(o1Value) !== JSON.stringify(o2Value)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function compareWearables(w1, w2) {
+ return compareObjects(w1, w2, [{'propertyName' : 'modelURL'},
+ {'propertyName' : 'parentJointIndex'},
+ {'propertyName' : 'marketplaceID'},
+ {'propertyName' : 'itemName'},
+ {'propertyName' : 'script'},
+ {'propertyName' : 'localPosition', 'comparer' : compareNumericObjects},
+ {'propertyName' : 'localRotationAngles', 'comparer' : compareNumericObjects},
+ {'propertyName' : 'dimensions', 'comparer' : compareNumericObjects}], 'properties')
+ }
+
+ function compareAttachments(a1, a2) {
+ return compareObjects(a1, a2, [{'propertyName' : 'position', 'comparer' : compareNumericObjects},
+ {'propertyName' : 'orientation'},
+ {'propertyName' : 'parentJointIndex'},
+ {'propertyName' : 'modelurl'}])
+ }
+
+ function findAvatarIndexByValue(avatar) {
+
+ var index = -1;
+
+ // 2DO: find better way of determining selected avatar in bookmarks
+ for(var i = 0; i < allAvatars.count; ++i) {
+ var thesame = true;
+ var bookmarkedAvatar = allAvatars.get(i);
+
+ if(bookmarkedAvatar.avatarUrl !== avatar.avatarUrl)
+ continue;
+
+ if(bookmarkedAvatar.avatarScale !== avatar.avatarScale)
+ continue;
+
+ if(!modelsAreEqual(bookmarkedAvatar.attachments, avatar.attachments, compareAttachments)) {
+ continue;
+ }
+
+ if(!modelsAreEqual(bookmarkedAvatar.wearables, avatar.wearables, compareWearables)) {
+ continue;
+ }
+
+ if(thesame) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function findAvatarIndex(avatarName) {
+ for(var i = 0; i < count; ++i) {
+ if(get(i).name === avatarName) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ function findAvatar(avatarName) {
+ var avatarIndex = findAvatarIndex(avatarName);
+ if(avatarIndex === -1)
+ return undefined;
+
+ return get(avatarIndex);
+ }
+
+}
diff --git a/interface/resources/qml/hifi/avatarapp/BlueButton.qml b/interface/resources/qml/hifi/avatarapp/BlueButton.qml
new file mode 100644
index 0000000000..e668951517
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/BlueButton.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.5
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+
+HifiControlsUit.Button {
+ HifiConstants {
+ id: hifi
+ }
+
+ width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20)
+ fontSize: 18
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.light;
+ height: 40
+}
diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml
new file mode 100644
index 0000000000..ab995e79e8
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml
@@ -0,0 +1,184 @@
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+import "../../controls" as HifiControls
+
+Rectangle {
+ id: root;
+ visible: false;
+ anchors.fill: parent;
+ color: Qt.rgba(0, 0, 0, 0.5);
+ z: 999;
+
+ property string titleText: 'Create Favorite'
+ property string favoriteNameText: favoriteName.text
+ property string avatarImageUrl: null
+ property int wearablesCount: 0
+
+ property string button1color: hifi.buttons.noneBorderlessGray;
+ property string button1text: 'CANCEL'
+ property string button2color: hifi.buttons.blue;
+ property string button2text: 'CONFIRM'
+
+ property var avatars;
+ property var onSaveClicked;
+ property var onCancelClicked;
+
+ function open(wearables, thumbnail) {
+ favoriteName.text = '';
+ favoriteName.forceActiveFocus();
+
+ avatarImageUrl = thumbnail;
+ wearablesCount = wearables;
+
+ visible = true;
+ }
+
+ function close() {
+ console.debug('closing');
+ visible = false;
+ }
+
+ HifiConstants {
+ id: hifi
+ }
+
+ // This object is always used in a popup.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ hoverEnabled: true;
+ }
+
+ Rectangle {
+ id: mainContainer;
+ width: Math.max(parent.width * 0.8, 400)
+ property int margin: 30;
+
+ height: childrenRect.height + margin * 2
+ onHeightChanged: {
+ console.debug('mainContainer: height = ', height)
+ }
+
+ anchors.centerIn: parent
+
+ color: "white"
+
+ // TextStyle1
+ RalewaySemiBold {
+ id: title
+ size: 24;
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: 30
+ anchors.leftMargin: 30
+ anchors.rightMargin: 30
+
+ text: root.titleText
+ }
+
+ Item {
+ id: contentContainer
+ width: parent.width - 50
+ height: childrenRect.height
+
+ anchors.top: title.bottom
+ anchors.topMargin: 20
+ anchors.left: parent.left;
+ anchors.leftMargin: 30;
+ anchors.right: parent.right;
+ anchors.rightMargin: 30;
+
+ AvatarThumbnail {
+ id: avatarThumbnail
+ avatarUrl: avatarImageUrl
+ onAvatarUrlChanged: {
+ console.debug('CreateFavoritesDialog: onAvatarUrlChanged: ', avatarUrl);
+ }
+
+ wearablesCount: avatarWearablesCount
+ }
+
+ InputTextStyle4 {
+ id: favoriteName
+ anchors.right: parent.right
+ height: 40
+ anchors.left: avatarThumbnail.right
+ anchors.leftMargin: 44
+ anchors.verticalCenter: avatarThumbnail.verticalCenter
+ placeholderText: "Enter Favorite Name"
+
+ RalewayRegular {
+ id: wrongName
+ anchors.top: parent.bottom;
+ anchors.topMargin: 2
+
+ anchors.left: parent.left
+ anchors.right: parent.right;
+ anchors.rightMargin: -contentContainer.anchors.rightMargin // allow text to render beyond favorite input text
+ wrapMode: Text.WordWrap
+ text: 'Favorite name exists. Overwrite existing favorite?'
+ size: 15
+ color: 'red'
+ visible: {
+ for(var i = 0; i < avatars.count; ++i) {
+ var avatarName = avatars.get(i).name;
+ if(avatarName === favoriteName.text) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+ }
+
+ DialogButtons {
+ anchors.top: contentContainer.bottom
+ anchors.topMargin: 20
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: 30
+
+ yesButton.enabled: favoriteNameText !== ''
+ yesText: root.button2text
+ noText: root.button1text
+
+ Binding on yesButton.text {
+ when: wrongName.visible
+ value: "OVERWRITE";
+ }
+
+ Binding on yesButton.color {
+ when: wrongName.visible
+ value: hifi.buttons.red;
+ }
+
+ Binding on yesButton.colorScheme {
+ when: wrongName.visible
+ value: hifi.colorSchemes.dark;
+ }
+
+ onYesClicked: function() {
+ if(onSaveClicked) {
+ onSaveClicked();
+ } else {
+ root.close();
+ }
+ }
+
+ onNoClicked: function() {
+ if(onCancelClicked) {
+ onCancelClicked();
+ } else {
+ root.close();
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/DialogButtons.qml b/interface/resources/qml/hifi/avatarapp/DialogButtons.qml
new file mode 100644
index 0000000000..46c17bb4dc
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/DialogButtons.qml
@@ -0,0 +1,41 @@
+import QtQuick 2.9
+
+Row {
+ id: root
+ property string yesText;
+ property string noText;
+ property var onYesClicked;
+ property var onNoClicked;
+
+ property alias yesButton: yesButton
+ property alias noButton: noButton
+
+ height: childrenRect.height
+ layoutDirection: Qt.RightToLeft
+
+ spacing: 30
+
+ BlueButton {
+ id: yesButton;
+ text: yesText;
+ onClicked: {
+ console.debug('bluebutton.clicked', onYesClicked);
+
+ if(onYesClicked) {
+ onYesClicked();
+ }
+ }
+ }
+
+ WhiteButton {
+ id: noButton
+ text: noText;
+ onClicked: {
+ console.debug('whitebutton.clicked', onNoClicked);
+
+ if(onNoClicked) {
+ onNoClicked();
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/InputField.qml b/interface/resources/qml/hifi/avatarapp/InputField.qml
new file mode 100644
index 0000000000..905518ef0f
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/InputField.qml
@@ -0,0 +1,48 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+
+TextField {
+ id: textField
+
+ property bool error: false;
+ text: 'ThisIsDisplayName'
+
+ states: [
+ State {
+ name: "hovered"
+ when: textField.hovered && !textField.focus && !textField.error;
+ PropertyChanges { target: background; color: '#afafaf' }
+ },
+ State {
+ name: "focused"
+ when: textField.focus && !textField.error
+ PropertyChanges { target: background; color: '#f2f2f2' }
+ PropertyChanges { target: background; border.color: '#00b4ef' }
+ },
+ State {
+ name: "error"
+ when: textField.error
+ PropertyChanges { target: background; color: '#f2f2f2' }
+ PropertyChanges { target: background; border.color: '#e84e62' }
+ }
+ ]
+
+ background: Rectangle {
+ id: background
+ implicitWidth: 200
+ implicitHeight: 40
+ color: '#d4d4d4'
+ border.color: '#afafaf'
+ border.width: 1
+ radius: 2
+ }
+
+ HiFiGlyphs {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ size: 36
+ text: "\ue00d"
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml
new file mode 100644
index 0000000000..4b868b47ce
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml
@@ -0,0 +1,16 @@
+import "../../controls-uit" as HifiControlsUit
+import "../../styles-uit"
+
+import QtQuick 2.0
+import QtQuick.Controls 2.2
+
+HifiControlsUit.TextField {
+ id: control
+ font.family: "Fira Sans"
+ font.pixelSize: 15;
+ implicitHeight: 40
+
+ AvatarAppStyle {
+ id: style
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml
new file mode 100644
index 0000000000..f2df0b5199
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml
@@ -0,0 +1,184 @@
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+import "../../controls" as HifiControls
+
+Rectangle {
+ id: root;
+ visible: false;
+ anchors.fill: parent;
+ color: Qt.rgba(0, 0, 0, 0.5);
+ z: 999;
+
+ property string titleText: ''
+ property string bodyText: ''
+ property alias inputText: input;
+
+ property string imageSource: null
+ onImageSourceChanged: {
+ console.debug('imageSource = ', imageSource)
+ }
+
+ property string button1color: hifi.buttons.noneBorderlessGray;
+ property string button1text: ''
+ property string button2color: hifi.buttons.blue;
+ property string button2text: ''
+
+ property var onButton2Clicked;
+ property var onButton1Clicked;
+ property var onLinkClicked;
+
+ function open() {
+ visible = true;
+ }
+
+ function close() {
+ visible = false;
+
+ onButton1Clicked = null;
+ onButton2Clicked = null;
+ button1text = '';
+ button2text = '';
+ imageSource = null;
+ inputText.visible = false;
+ inputText.placeholderText = '';
+ inputText.text = '';
+ }
+
+ HifiConstants {
+ id: hifi
+ }
+
+ // This object is always used in a popup.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ hoverEnabled: true;
+ }
+
+ Rectangle {
+ id: mainContainer;
+ width: Math.max(parent.width * 0.8, 400)
+ property int margin: 30;
+
+ height: childrenRect.height + margin * 2
+ onHeightChanged: {
+ console.debug('mainContainer: height = ', height)
+ }
+
+ anchors.centerIn: parent
+
+ color: "white"
+
+ // TextStyle1
+ RalewaySemiBold {
+ id: title
+ size: 24;
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: 30
+ anchors.leftMargin: 30
+ anchors.rightMargin: 30
+
+ text: root.titleText
+ elide: Qt.ElideRight
+ }
+
+ Column {
+ id: contentContainer
+ spacing: 15
+
+ anchors.top: title.bottom
+ anchors.topMargin: 10
+ anchors.left: parent.left;
+ anchors.leftMargin: 30;
+ anchors.right: parent.right;
+ anchors.rightMargin: 30;
+
+ InputTextStyle4 {
+ id: input
+ visible: false
+ height: visible ? implicitHeight : 0
+
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ }
+
+ // TextStyle3
+ RalewayRegular {
+ id: body
+
+ AvatarAppStyle {
+ id: style
+ }
+
+ size: 18
+ text: root.bodyText;
+ linkColor: style.colors.blueHighlight
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: paintedHeight;
+ verticalAlignment: Text.AlignTop;
+ wrapMode: Text.WordWrap;
+
+ onLinkActivated: {
+ if(onLinkClicked)
+ onLinkClicked(link);
+ }
+ }
+
+ Image {
+ id: image
+ Binding on height {
+ when: imageSource === null
+ value: 0
+ }
+
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+
+ Binding on source {
+ when: imageSource !== null
+ value: imageSource
+ }
+
+ visible: imageSource !== null ? true : false
+ }
+ }
+
+ DialogButtons {
+ id: buttons
+
+ anchors.top: contentContainer.bottom
+ anchors.topMargin: 30
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: 30
+
+ yesButton.enabled: !input.visible || input.text.length !== 0
+ yesText: root.button2text
+ noText: root.button1text
+
+ onYesClicked: function() {
+ if(onButton2Clicked) {
+ onButton2Clicked();
+ } else {
+ root.close();
+ }
+ }
+
+ onNoClicked: function() {
+ if(onButton1Clicked) {
+ onButton1Clicked();
+ } else {
+ root.close();
+ }
+ }
+ }
+
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
new file mode 100644
index 0000000000..125b30fa95
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
@@ -0,0 +1,122 @@
+import QtQuick 2.5
+
+MessageBox {
+ id: popup
+
+ function showSpecifyAvatarUrl(callback, linkCallback) {
+ popup.onButton2Clicked = callback;
+ popup.titleText = 'Specify Avatar URL'
+ popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one. ' +
+ '' +
+ 'Learn to make a custom avatar by opening this link on your desktop.' +
+ ' '
+ popup.inputText.visible = true;
+ popup.inputText.placeholderText = 'Enter Avatar Url';
+ popup.button1text = 'CANCEL';
+ popup.button2text = 'CONFIRM';
+
+ popup.onButton2Clicked = function() {
+ if(callback)
+ callback();
+
+ popup.close();
+ }
+
+ popup.onLinkClicked = function(link) {
+ if(linkCallback)
+ linkCallback(link);
+ }
+
+ popup.open();
+ popup.inputText.forceActiveFocus();
+ }
+
+ property url getWearablesUrl: '../../../images/avatarapp/AvatarIsland.jpg'
+
+ function showGetWearables(callback, linkCallback) {
+ popup.button2text = 'AvatarIsland'
+ popup.button1text = 'CANCEL'
+ popup.titleText = 'Get Wearables'
+ popup.bodyText = 'Buy wearables from Marketplace ' + ' ' +
+ 'Wear wearables from My Purchases ' + ' ' +
+ 'You can visit the domain “AvatarIsland” to get wearables'
+
+ popup.imageSource = getWearablesUrl;
+ popup.onButton2Clicked = function() {
+ popup.close();
+
+ if(callback)
+ callback();
+ }
+
+ popup.onLinkClicked = function(link) {
+ popup.close();
+
+ if(linkCallback)
+ linkCallback(link);
+ }
+
+ popup.open();
+ }
+
+ function showDeleteFavorite(favoriteName, callback) {
+ popup.titleText = 'Delete Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName)
+ popup.bodyText = 'This will delete your favorite. You will retain access to the wearables and avatar that made up the favorite from My Purchases.'
+ popup.imageSource = null;
+ popup.button1text = 'CANCEL'
+ popup.button2text = 'DELETE'
+
+ popup.onButton2Clicked = function() {
+ popup.close();
+
+ if(callback)
+ callback();
+ }
+ popup.open();
+ }
+
+ function showLoadFavorite(favoriteName, callback) {
+ popup.button2text = 'CONFIRM'
+ popup.button1text = 'CANCEL'
+ popup.titleText = 'Load Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName)
+ popup.bodyText = 'This will switch your current avatar and wearables that you are wearing with a new avatar and wearables.'
+ popup.imageSource = null;
+ popup.onButton2Clicked = function() {
+ popup.close();
+
+ if(callback)
+ callback();
+ }
+ popup.open();
+ }
+
+ property url getAvatarsUrl: '../../../images/avatarapp/BodyMart.PNG'
+
+ function showBuyAvatars(callback, linkCallback) {
+ popup.button2text = 'BodyMart'
+ popup.button1text = 'CANCEL'
+ popup.titleText = 'Get Avatars'
+
+ popup.bodyText = 'Buy avatars from Marketplace ' + ' ' +
+ 'Wear avatars from My Purchases ' + ' ' +
+ 'You can visit the domain “BodyMart” to get avatars'
+
+ popup.imageSource = getAvatarsUrl;
+ popup.onButton2Clicked = function() {
+ popup.close();
+
+ if(callback)
+ callback();
+ }
+
+ popup.onLinkClicked = function(link) {
+ popup.close();
+
+ if(linkCallback)
+ linkCallback(link);
+ }
+
+ popup.open();
+ }
+}
+
diff --git a/interface/resources/qml/hifi/avatarapp/PageIndicator.qml b/interface/resources/qml/hifi/avatarapp/PageIndicator.qml
new file mode 100644
index 0000000000..28575e4496
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/PageIndicator.qml
@@ -0,0 +1,44 @@
+import QtQuick 2.9
+
+ShadowGlyph {
+ id: indicator
+ property bool isPrevious: true;
+ property bool hasNext: false
+ property bool hasPrev: false
+ property bool isEnabled: isPrevious ? hasPrev : hasNext
+ signal clicked;
+
+ states: [
+ State {
+ name: "hovered"
+ when: pageIndicatorMouseArea.containsMouse;
+ PropertyChanges { target: pageIndicatorMouseArea; anchors.bottomMargin: -5 }
+ PropertyChanges { target: indicator; y: -5 }
+ PropertyChanges { target: indicator; dropShadowVerticalOffset: 9 }
+ }
+ ]
+
+ Behavior on y {
+ NumberAnimation {
+ duration: 100
+ }
+ }
+
+ text: isPrevious ? "E" : "D";
+ width: 40
+ height: 40
+ font.pixelSize: 100
+ color: isEnabled ? 'black' : 'gray'
+ visible: hasNext || hasPrev
+ horizontalAlignment: Text.AlignHCenter
+
+ MouseArea {
+ id: pageIndicatorMouseArea
+ anchors.fill: parent
+ enabled: isEnabled
+ hoverEnabled: enabled
+ onClicked: {
+ parent.clicked();
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/RoundImage.qml b/interface/resources/qml/hifi/avatarapp/RoundImage.qml
new file mode 100644
index 0000000000..f941cd0f1d
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/RoundImage.qml
@@ -0,0 +1,36 @@
+import QtQuick 2.0
+
+Item {
+
+ property alias border: borderRectangle.border
+ property alias source: image.source
+ property alias fillMode: image.fillMode
+ property alias radius: mask.radius
+ property alias status: image.status
+ property alias progress: image.progress
+
+ Image {
+ id: image
+ anchors.fill: parent
+ anchors.margins: borderRectangle.border.width
+ }
+
+ Rectangle {
+ id: mask
+ anchors.fill: image
+ }
+
+ TransparencyMask {
+ anchors.fill: image
+ source: image
+ maskSource: mask
+ }
+
+ Rectangle {
+ id: borderRectangle
+ anchors.fill: parent
+
+ radius: mask.radius
+ color: "transparent"
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml
new file mode 100644
index 0000000000..f996bdfd03
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/Settings.qml
@@ -0,0 +1,353 @@
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Layouts 1.3
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+import "../../controls" as HifiControls
+
+Rectangle {
+ id: root
+
+ color: 'white'
+ visible: false;
+
+ signal scaleChanged(real scale);
+
+ property alias onSaveClicked: dialogButtons.onYesClicked
+ property alias onCancelClicked: dialogButtons.onNoClicked
+
+ property real scaleValue: scaleSlider.value / 10
+ property alias dominantHandIsLeft: leftHandRadioButton.checked
+ property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked
+ property alias avatarAnimationJSON: avatarAnimationUrlInputText.text
+ property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
+
+ property real avatarScaleBackup;
+ function open(settings, avatarScale) {
+ console.debug('Settings.qml: open: ', JSON.stringify(settings, 0, 4));
+ avatarScaleBackup = avatarScale;
+
+ scaleSlider.notify = false;
+ scaleSlider.value = Math.round(avatarScale * 10);
+ scaleSlider.notify = true;;
+
+ if(settings.dominantHand === 'left') {
+ leftHandRadioButton.checked = true;
+ } else {
+ rightHandRadioButton.checked = true;
+ }
+
+ if(settings.collisionsEnabled) {
+ collisionsEnabledRadiobutton.checked = true;
+ } else {
+ collisionsDisabledRadioButton.checked = true;
+ }
+
+ avatarAnimationJSON = settings.animGraphUrl;
+ avatarCollisionSoundUrl = settings.collisionSoundUrl;
+
+ visible = true;
+ }
+
+ function close() {
+ visible = false
+ }
+
+ // This object is always used in a popup.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ hoverEnabled: true;
+ }
+
+ Item {
+ anchors.left: parent.left
+ anchors.leftMargin: 27
+ anchors.top: parent.top
+ anchors.topMargin: 25
+ anchors.right: parent.right
+ anchors.rightMargin: 32
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 57
+
+ RowLayout {
+ id: avatarScaleRow
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ spacing: 17
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ text: "Avatar Scale"
+ verticalAlignment: Text.AlignVCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ RowLayout {
+ anchors.verticalCenter: parent.verticalCenter
+ Layout.fillWidth: true
+
+ spacing: 0
+
+ HiFiGlyphs {
+ size: 30
+ text: 'T'
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ HifiControlsUit.Slider {
+ id: scaleSlider
+ property bool notify: false;
+
+ from: 1
+ to: 40
+
+ onValueChanged: {
+ console.debug('value changed: ', value);
+ if(notify) {
+ console.debug('notifying.. ');
+ root.scaleChanged(value / 10);
+ }
+ }
+
+ anchors.verticalCenter: parent.verticalCenter
+ Layout.fillWidth: true
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ anchors.left: scaleSlider.left
+ anchors.leftMargin: 5
+ anchors.top: scaleSlider.bottom
+ anchors.topMargin: 2
+ text: String(scaleSlider.from / 10) + 'x'
+ }
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ anchors.right: scaleSlider.right
+ anchors.rightMargin: 5
+ anchors.top: scaleSlider.bottom
+ anchors.topMargin: 2
+ text: String(scaleSlider.to / 10) + 'x'
+ }
+ }
+
+ HiFiGlyphs {
+ size: 40
+ text: 'T'
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ ShadowRectangle {
+ width: 37
+ height: 28
+ AvatarAppStyle {
+ id: style
+ }
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: style.colors.blueHighlight }
+ GradientStop { position: 1.0; color: style.colors.blueAccent }
+ }
+
+ radius: 3
+
+ RalewaySemiBold {
+ color: 'white'
+ anchors.centerIn: parent
+ text: "1x"
+ size: 18
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ scaleSlider.value = 10
+ }
+ }
+ }
+ }
+
+ GridLayout {
+ id: handAndCollisions
+ anchors.top: avatarScaleRow.bottom
+ anchors.topMargin: 39
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ rows: 2
+ rowSpacing: 25
+
+ columns: 3
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ Layout.row: 0
+ Layout.column: 0
+
+ text: "Dominant Hand"
+ }
+
+ ButtonGroup {
+ id: leftRight
+ }
+
+ HifiControlsUit.RadioButton {
+ id: leftHandRadioButton
+
+ Layout.row: 0
+ Layout.column: 1
+ Layout.leftMargin: -40
+
+ ButtonGroup.group: leftRight
+ checked: true
+
+ colorScheme: hifi.colorSchemes.light
+ fontSize: 17
+ letterSpacing: 1.4
+ text: "Left"
+ boxSize: 20
+ }
+
+ HifiControlsUit.RadioButton {
+ id: rightHandRadioButton
+
+ Layout.row: 0
+ Layout.column: 2
+ Layout.rightMargin: 20
+
+ ButtonGroup.group: leftRight
+
+ colorScheme: hifi.colorSchemes.light
+ fontSize: 17
+ letterSpacing: 1.4
+ text: "Right"
+ boxSize: 20
+ }
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ Layout.row: 1
+ Layout.column: 0
+
+ text: "Avatar Collisions"
+ }
+
+ ButtonGroup {
+ id: onOff
+ }
+
+ HifiControlsUit.RadioButton {
+ id: collisionsEnabledRadiobutton
+
+ Layout.row: 1
+ Layout.column: 1
+ Layout.leftMargin: -40
+ ButtonGroup.group: onOff
+
+ colorScheme: hifi.colorSchemes.light
+ fontSize: 17
+ letterSpacing: 1.4
+ checked: true
+
+ text: "ON"
+ boxSize: 20
+ }
+
+ HifiConstants {
+ id: hifi
+ }
+
+ HifiControlsUit.RadioButton {
+ id: collisionsDisabledRadioButton
+
+ Layout.row: 1
+ Layout.column: 2
+ Layout.rightMargin: 20
+
+ ButtonGroup.group: onOff
+ colorScheme: hifi.colorSchemes.light
+ fontSize: 17
+ letterSpacing: 1.4
+
+ text: "OFF"
+ boxSize: 20
+ }
+ }
+
+ ColumnLayout {
+ id: avatarAnimationLayout
+ anchors.top: handAndCollisions.bottom
+ anchors.topMargin: 25
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ spacing: 4
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ text: "Avatar Animation JSON"
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ InputTextStyle4 {
+ id: avatarAnimationUrlInputText
+ font.pixelSize: 17
+ anchors.left: parent.left
+ anchors.right: parent.right
+ placeholderText: 'user\\file\\dir'
+ }
+ }
+
+ ColumnLayout {
+ id: avatarCollisionLayout
+ anchors.top: avatarAnimationLayout.bottom
+ anchors.topMargin: 25
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ spacing: 4
+
+ // TextStyle9
+ RalewaySemiBold {
+ size: 17;
+ text: "Avatar collision sound URL (optional)"
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ InputTextStyle4 {
+ id: avatarCollisionSoundUrlInputText
+ font.pixelSize: 17
+ anchors.left: parent.left
+ anchors.right: parent.right
+ placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-'
+ }
+ }
+
+ DialogButtons {
+ id: dialogButtons
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ yesText: "SAVE"
+ noText: "CANCEL"
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml
new file mode 100644
index 0000000000..c2d84bb371
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml
@@ -0,0 +1,29 @@
+import "../../styles-uit"
+import QtQuick 2.9
+import QtGraphicalEffects 1.0
+
+Item {
+ property alias text: glyph.text
+ property alias font: glyph.font
+ property alias color: glyph.color
+ property alias horizontalAlignment: glyph.horizontalAlignment
+ property alias dropShadowRadius: shadow.radius
+ property alias dropShadowHorizontalOffset: shadow.horizontalOffset
+ property alias dropShadowVerticalOffset: shadow.verticalOffset
+
+ HiFiGlyphs {
+ id: glyph
+ width: parent.width
+ height: parent.height
+ }
+
+ DropShadow {
+ id: shadow
+ anchors.fill: glyph
+ radius: 4
+ horizontalOffset: 0
+ verticalOffset: 4
+ color: Qt.rgba(0, 0, 0, 0.25)
+ source: glyph
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml
new file mode 100644
index 0000000000..3995446e49
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml
@@ -0,0 +1,32 @@
+import "../../styles-uit"
+import QtQuick 2.9
+import QtGraphicalEffects 1.0
+
+Item {
+ property alias source: image.source
+ property alias dropShadowRadius: shadow.radius
+ property alias dropShadowHorizontalOffset: shadow.horizontalOffset
+ property alias dropShadowVerticalOffset: shadow.verticalOffset
+ property alias radius: image.radius
+ property alias border: image.border
+ property alias status: image.status
+ property alias progress: image.progress
+ property alias fillMode: image.fillMode
+
+ RoundImage {
+ id: image
+ width: parent.width
+ height: parent.height
+ radius: 6
+ }
+
+ DropShadow {
+ id: shadow
+ anchors.fill: image
+ radius: 6
+ horizontalOffset: 0
+ verticalOffset: 3
+ color: Qt.rgba(0, 0, 0, 0.25)
+ source: image
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml
new file mode 100644
index 0000000000..741fce3d8d
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml
@@ -0,0 +1,30 @@
+import "../../styles-uit"
+import QtQuick 2.9
+import QtGraphicalEffects 1.0
+
+Item {
+ property alias color: rectangle.color
+ property alias gradient: rectangle.gradient
+ property alias border: rectangle.border
+ property alias radius: rectangle.radius
+ property alias dropShadowRadius: shadow.radius
+ property alias dropShadowHorizontalOffset: shadow.horizontalOffset
+ property alias dropShadowVerticalOffset: shadow.verticalOffset
+ property alias dropShadowOpacity: shadow.opacity
+
+ Rectangle {
+ id: rectangle
+ width: parent.width
+ height: parent.height
+ }
+
+ DropShadow {
+ id: shadow
+ anchors.fill: rectangle
+ radius: 6
+ horizontalOffset: 0
+ verticalOffset: 3
+ color: Qt.rgba(0, 0, 0, 0.25)
+ source: rectangle
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/Spinner.qml b/interface/resources/qml/hifi/avatarapp/Spinner.qml
new file mode 100644
index 0000000000..3fc331346d
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/Spinner.qml
@@ -0,0 +1,7 @@
+import QtQuick 2.5
+import QtWebEngine 1.5
+
+AnimatedImage {
+ source: "../../../icons/loader-snake-64-w.gif"
+ playing: visible
+}
diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml
new file mode 100644
index 0000000000..3c5463e1dd
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml
@@ -0,0 +1,29 @@
+import "../../styles-uit"
+import QtQuick 2.9
+import QtGraphicalEffects 1.0
+
+ShadowRectangle {
+ width: 44
+ height: 28
+ AvatarAppStyle {
+ id: style
+ }
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: style.colors.blueHighlight }
+ GradientStop { position: 1.0; color: style.colors.blueAccent }
+ }
+
+ property alias glyphText: glyph.text
+ property alias glyphRotation: glyph.rotation
+ property alias glyphSize: glyph.size
+
+ radius: 3
+
+ HiFiGlyphs {
+ id: glyph
+ color: 'white'
+ anchors.centerIn: parent
+ size: 30
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml
new file mode 100644
index 0000000000..4884d1e1ad
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml
@@ -0,0 +1,43 @@
+import QtQuick 2.0
+
+Item {
+ property alias source: sourceImage.sourceItem
+ property alias maskSource: sourceMask.sourceItem
+
+ anchors.fill: parent
+ ShaderEffectSource {
+ id: sourceMask
+ smooth: true
+ hideSource: true
+ }
+ ShaderEffectSource {
+ id: sourceImage
+ hideSource: true
+ }
+
+ ShaderEffect {
+ id: maskEffect
+ anchors.fill: parent
+
+ property variant source: sourceImage
+ property variant mask: sourceMask
+
+ fragmentShader: {
+"
+ varying highp vec2 qt_TexCoord0;
+ uniform lowp sampler2D source;
+ uniform lowp sampler2D mask;
+ void main() {
+
+ highp vec4 maskColor = texture2D(mask, vec2(qt_TexCoord0.x, qt_TexCoord0.y));
+ highp vec4 sourceColor = texture2D(source, vec2(qt_TexCoord0.x, qt_TexCoord0.y));
+
+ if(maskColor.a > 0.0)
+ gl_FragColor = sourceColor;
+ else
+ gl_FragColor = maskColor;
+ }
+"
+ }
+ }
+}
\ No newline at end of file
diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml
new file mode 100644
index 0000000000..33e82fe0ee
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml
@@ -0,0 +1,65 @@
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+import "../../controls" as HifiControls
+
+Row {
+ id: root
+ width: parent.width
+ height: xspinner.controlHeight
+
+ property int spinboxSpace: 10
+ property int spinboxWidth: (parent.width - 2 * spinboxSpace) / 3
+ property color backgroundColor: "darkgray"
+
+ property int decimals: 4
+ property real realFrom: 0
+ property real realTo: 100
+ property real realStepSize: 0.0001
+
+ spacing: spinboxSpace
+
+ property alias xvalue: xspinner.realValue
+ property alias yvalue: yspinner.realValue
+ property alias zvalue: zspinner.realValue
+
+ HifiControlsUit.SpinBox {
+ id: xspinner
+ width: parent.spinboxWidth
+ labelInside: "X:"
+ backgroundColor: parent.backgroundColor
+ colorLabelInside: hifi.colors.redHighlight
+ colorScheme: hifi.colorSchemes.light
+ decimals: root.decimals;
+ realFrom: root.realFrom
+ realTo: root.realTo
+ realStepSize: root.realStepSize
+ }
+
+ HifiControlsUit.SpinBox {
+ id: yspinner
+ width: parent.spinboxWidth
+ labelInside: "Y:"
+ backgroundColor: parent.backgroundColor
+ colorLabelInside: hifi.colors.greenHighlight
+ colorScheme: hifi.colorSchemes.light
+ decimals: root.decimals;
+ realFrom: root.realFrom
+ realTo: root.realTo
+ realStepSize: root.realStepSize
+ }
+
+ HifiControlsUit.SpinBox {
+ id: zspinner
+ width: parent.spinboxWidth
+ labelInside: "Z:"
+ backgroundColor: parent.backgroundColor
+ colorLabelInside: hifi.colors.primaryHighlight
+ colorScheme: hifi.colorSchemes.light
+ decimals: root.decimals;
+ realFrom: root.realFrom
+ realTo: root.realTo
+ realStepSize: root.realStepSize
+ }
+}
diff --git a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml
new file mode 100644
index 0000000000..dc729ae097
--- /dev/null
+++ b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.5
+import "../../styles-uit"
+import "../../controls-uit" as HifiControlsUit
+
+HifiControlsUit.Button {
+ HifiConstants {
+ id: hifi
+ }
+
+ width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20)
+ fontSize: 18
+ color: hifi.buttons.noneBorderlessGray;
+ colorScheme: hifi.colorSchemes.light;
+ height: 40
+}
diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
index 16c1b55930..cac62d3976 100644
--- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml
@@ -92,9 +92,9 @@ Rectangle {
onBuyResult: {
if (result.status !== 'success') {
- failureErrorText.text = result.message;
+ failureErrorText.text = result.data.message;
root.activeView = "checkoutFailure";
- UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
+ UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.data.message);
} else {
root.certificateId = result.data.certificate_id;
root.itemHref = result.data.download_url;
@@ -129,7 +129,7 @@ Rectangle {
}
onAppInstalled: {
- if (appHref === root.itemHref) {
+ if (appID === root.itemId) {
root.isInstalled = true;
}
}
diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
index 3e4bae4780..a515c8031f 100644
--- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
+++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml
@@ -398,7 +398,7 @@ Item {
http: root.http;
listModelName: root.listModelName;
endpoint: "/api/v1/users?filter=connections";
- itemsPerPage: 8;
+ itemsPerPage: 9;
listView: connectionsList;
processPage: function (data) {
return data.users;
@@ -520,7 +520,6 @@ Item {
visible: !connectionsLoading.visible;
clip: true;
model: connectionsModel;
- onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); }
snapMode: ListView.SnapToItem;
// Anchors
anchors.fill: parent;
diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
index 9f1d307f0e..b2338d08de 100644
--- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
@@ -67,13 +67,13 @@ Item {
}
onAppInstalled: {
- if (appHref === root.itemHref) {
+ if (appID === root.itemId) {
root.isInstalled = true;
}
}
onAppUninstalled: {
- if (appHref === root.itemHref) {
+ if (appID === root.itemId) {
root.isInstalled = false;
}
}
@@ -328,7 +328,15 @@ Item {
item.buttonColor = "#E2334D";
item.buttonClicked = function() {
sendToPurchases({ method: 'flipCard', closeAll: true });
- sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl});
+ sendToPurchases({
+ method: 'updateItemClicked',
+ itemId: root.itemId,
+ itemEdition: root.itemEdition,
+ upgradeUrl: root.upgradeUrl,
+ itemHref: root.itemHref,
+ itemType: root.itemType,
+ isInstalled: root.isInstalled
+ });
}
}
}
@@ -723,7 +731,7 @@ Item {
}
HiFiGlyphs {
id: rezIcon;
- text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
+ text: root.isInstalled ? "" : (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
anchors.right: rezIconLabel.left;
anchors.rightMargin: 2;
anchors.verticalCenter: parent.verticalCenter;
diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
index 8a5b1fb0e7..3569ce6767 100644
--- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
@@ -98,7 +98,7 @@ Rectangle {
}
onAppInstalled: {
- root.installedApps = Commerce.getInstalledApps();
+ root.installedApps = Commerce.getInstalledApps(appID);
}
onAppUninstalled: {
@@ -551,8 +551,9 @@ Rectangle {
HifiModels.PSFListModel {
id: purchasesModel;
- itemsPerPage: 6;
+ itemsPerPage: 7;
listModelName: 'purchases';
+ listView: purchasesContentsList;
getPage: function () {
console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage);
Commerce.inventory(
@@ -706,7 +707,23 @@ Rectangle {
}
}
} else if (msg.method === "updateItemClicked") {
- sendToScript(msg);
+ if (msg.itemType === "app" && msg.isInstalled) {
+ lightboxPopup.titleText = "Uninstall App";
+ lightboxPopup.bodyText = "The app that you are trying to update is installed. " +
+ "If you proceed, the current version of the app will be uninstalled.";
+ lightboxPopup.button1text = "CANCEL";
+ lightboxPopup.button1method = function() {
+ lightboxPopup.visible = false;
+ }
+ lightboxPopup.button2text = "CONFIRM";
+ lightboxPopup.button2method = function() {
+ Commerce.uninstallApp(msg.itemHref);
+ sendToScript(msg);
+ };
+ lightboxPopup.visible = true;
+ } else {
+ sendToScript(msg);
+ }
} else if (msg.method === "giftAsset") {
sendAsset.assetName = msg.itemName;
sendAsset.assetCertID = msg.certId;
@@ -765,14 +782,6 @@ Rectangle {
}
}
}
-
-
- onAtYEndChanged: {
- if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) {
- console.log("User scrolled to the bottom of 'Purchases'.");
- purchasesModel.getNextPage();
- }
- }
}
Rectangle {
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
index 3e0a56b4c5..a0c6057b3b 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -212,6 +212,7 @@ Item {
HifiModels.PSFListModel {
id: transactionHistoryModel;
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
+ listView: transactionHistory;
itemsPerPage: 6;
getPage: function () {
console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve);
@@ -346,12 +347,6 @@ Item {
}
}
}
- onAtYEndChanged: {
- if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) {
- console.log("User scrolled to the bottom of 'Recent Activity'.");
- transactionHistoryModel.getNextPage();
- }
- }
}
Item {
diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml
index cb4913f999..861de001d8 100644
--- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml
+++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml
@@ -17,7 +17,7 @@ PreferencesDialog {
id: root
objectName: "GeneralPreferencesDialog"
title: "General Settings"
- showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"]
+ showCategories: ["User Interface", "Mouse Sensitivity", "HMD", "Snapshots", "Privacy"]
property var settings: Settings {
category: root.objectName
property alias x: root.x
diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml
index 1bfa2f6ae0..988502dd91 100644
--- a/interface/resources/qml/hifi/models/PSFListModel.qml
+++ b/interface/resources/qml/hifi/models/PSFListModel.qml
@@ -33,7 +33,6 @@ ListModel {
// QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early.
property bool initialized: false;
- Component.onCompleted: initialized = true;
onEndpointChanged: if (initialized) { getFirstPage('delayClear'); }
onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); }
onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); }
@@ -51,6 +50,8 @@ ListModel {
if (!delayedClear) { root.clear(); }
currentPageToRetrieve = 1;
retrievedAtLeastOnePage = false;
+ totalPages = 0;
+ totalEntries = 0;
}
// Page processing.
@@ -58,7 +59,39 @@ ListModel {
// Override to return one property of data, and/or to transform the elements. Must return an array of model elements.
property var processPage: function (data) { return data; }
- property var listView; // Optional. For debugging.
+ property var listView; // Optional. For debugging, or for having the scroll handler automatically call getNextPage.
+ property var flickable: listView && (listView.flickableItem || listView);
+ // 2: get two pages before you need it (i.e. one full page before you reach the end).
+ // 1: equivalent to paging when reaching end (and not before).
+ // 0: don't getNextPage on scroll at all here. The application code will do it.
+ property real pageAhead: 2.0;
+ function needsEarlyYFetch() {
+ return flickable
+ && !flickable.atYBeginning
+ && (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height));
+ }
+ function needsEarlyXFetch() {
+ return flickable
+ && !flickable.atXBeginning
+ && (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width));
+ }
+ function getNextPageIfHorizontalScroll() {
+ if (needsEarlyXFetch()) { getNextPage(); }
+ }
+ function getNextPageIfVerticalScroll() {
+ if (needsEarlyYFetch()) { getNextPage(); }
+ }
+ Component.onCompleted: {
+ initialized = true;
+ if (flickable && pageAhead > 0.0) {
+ // Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire.
+ flickable.contentXChanged.connect(getNextPageIfHorizontalScroll);
+ flickable.contentYChanged.connect(getNextPageIfVerticalScroll);
+ }
+ }
+
+ property int totalPages: 0;
+ property int totalEntries: 0;
// Check consistency and call processPage.
function handlePage(error, response) {
var processed;
@@ -79,8 +112,10 @@ ListModel {
if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property.
return fail("Mismatched page, expected:" + currentPageToRetrieve);
}
+ totalPages = response.total_pages || 0;
+ totalEntries = response.total_entries || 0;
processed = processPage(response.data || response);
- if (response.total_pages && (response.total_pages === currentPageToRetrieve)) {
+ if (totalPages && (totalPages === currentPageToRetrieve)) {
currentPageToRetrieve = -1;
}
diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml
index 4ad37b7bc8..135c1379e2 100644
--- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml
+++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml
@@ -69,6 +69,7 @@ Item {
id: stack
initialItem: inputConfiguration
property alias messageVisible: imageMessageBox.visible
+ property string selectedPlugin: ""
Rectangle {
id: inputConfiguration
anchors {
@@ -274,6 +275,8 @@ Item {
} else {
box.label = "";
}
+
+ stack.selectedPlugin = selectedDevice;
}
Timer {
@@ -297,6 +300,12 @@ Item {
id: controllerPrefereneces
objectName: "TabletControllerPreferences"
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
+ categoryProperties: {
+ "VR Movement" : {
+ "User real-world height (meters)" : { "anchors.right" : "undefined" },
+ "RESET SENSORS" : { "width" : "180", "anchors.left" : "undefined" }
+ }
+ }
}
}
}
diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml
new file mode 100644
index 0000000000..d484885103
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtWebChannel 1.0
+import "../../controls"
+import "../toolbars"
+import QtGraphicalEffects 1.0
+import "../../controls-uit" as HifiControls
+import "../../styles-uit"
+
+
+WebView {
+ id: entityListToolWebView
+ url: Paths.defaultScripts + "/system/html/entityList.html"
+ enabled: true
+}
diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml
index 9a7958f95c..4ac8755570 100644
--- a/interface/resources/qml/hifi/tablet/EditTabView.qml
+++ b/interface/resources/qml/hifi/tablet/EditTabView.qml
@@ -9,7 +9,6 @@ import "../../styles-uit"
TabBar {
id: editTabView
- // anchors.fill: parent
width: parent.width
contentWidth: parent.width
padding: 0
@@ -34,7 +33,7 @@ TabBar {
width: parent.width
clip: true
- contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
+ contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
assetServerButton.anchors.topMargin + importButton.anchors.topMargin +
header.paintedHeight
@@ -77,8 +76,9 @@ TabBar {
text: "MODEL"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newModelButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -88,8 +88,9 @@ TabBar {
text: "CUBE"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newCubeButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -99,8 +100,9 @@ TabBar {
text: "SPHERE"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newSphereButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -110,8 +112,9 @@ TabBar {
text: "LIGHT"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newLightButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -121,8 +124,9 @@ TabBar {
text: "TEXT"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newTextButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -132,8 +136,9 @@ TabBar {
text: "IMAGE"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newImageButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newImageButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -143,8 +148,9 @@ TabBar {
text: "WEB"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newWebButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -154,8 +160,9 @@ TabBar {
text: "ZONE"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newZoneButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -165,8 +172,9 @@ TabBar {
text: "PARTICLE"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newParticleButton" }
+ });
editTabView.currentIndex = 4
}
}
@@ -176,8 +184,9 @@ TabBar {
text: "MATERIAL"
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newMaterialButton" }
+ });
editTabView.currentIndex = 2
}
}
@@ -196,8 +205,9 @@ TabBar {
anchors.topMargin: 35
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "openAssetBrowserButton" }
+ });
}
}
@@ -214,8 +224,9 @@ TabBar {
anchors.topMargin: 20
onClicked: {
editRoot.sendToScript({
- method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" }
- });
+ method: "newEntityButtonClicked",
+ params: { buttonName: "importEntitiesButton" }
+ });
}
}
}
diff --git a/interface/resources/qml/hifi/tablet/EditTools.qml b/interface/resources/qml/hifi/tablet/EditTools.qml
new file mode 100644
index 0000000000..f989038c16
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/EditTools.qml
@@ -0,0 +1,58 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.3
+
+// FIXME pretty non-DRY code, should figure out a way to optionally hide one tab from the tab view, keep in sync with Edit.qml
+StackView {
+ id: editRoot
+ objectName: "stack"
+
+ signal sendToScript(var message);
+
+ topPadding: 40
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+
+ anchors.fill: parent
+
+ property var itemProperties: {"y": editRoot.topPadding,
+ "width": editRoot.availableWidth,
+ "height": editRoot.availableHeight }
+ Component.onCompleted: {
+ tab.currentIndex = 0
+ }
+
+ background: Rectangle {
+ color: "#404040" //default background color
+ EditToolsTabView {
+ id: tab
+ anchors.fill: parent
+ currentIndex: -1
+ onCurrentIndexChanged: {
+ editRoot.replace(null, tab.itemAt(currentIndex).visualItem,
+ itemProperties,
+ StackView.Immediate)
+ }
+ }
+ }
+
+ function pushSource(path) {
+ editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties,
+ StackView.Immediate);
+ editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
+ }
+
+ function popSource() {
+ editRoot.pop(StackView.Immediate);
+ }
+
+ // Passes script messages to the item on the top of the stack
+ function fromScript(message) {
+ var currentItem = editRoot.currentItem;
+ if (currentItem && currentItem.fromScript) {
+ currentItem.fromScript(message);
+ } else if (tab.fromScript) {
+ tab.fromScript(message);
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml
new file mode 100644
index 0000000000..00084b8ca9
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml
@@ -0,0 +1,328 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtWebChannel 1.0
+import "../../controls"
+import "../toolbars"
+import QtGraphicalEffects 1.0
+import "../../controls-uit" as HifiControls
+import "../../styles-uit"
+
+TabBar {
+ id: editTabView
+ width: parent.width
+ contentWidth: parent.width
+ padding: 0
+ spacing: 0
+
+ readonly property QtObject tabIndex: QtObject {
+ readonly property int create: 0
+ readonly property int properties: 1
+ readonly property int grid: 2
+ readonly property int particle: 3
+ }
+
+ readonly property HifiConstants hifi: HifiConstants {}
+
+ EditTabButton {
+ title: "CREATE"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ property Component visualItem: Component {
+
+ Rectangle {
+ color: "#404040"
+ id: container
+
+ Flickable {
+ height: parent.height
+ width: parent.width
+ clip: true
+
+ contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height +
+ header.anchors.topMargin + createEntitiesFlow.anchors.topMargin +
+ assetServerButton.anchors.topMargin + importButton.anchors.topMargin +
+ header.paintedHeight
+
+ contentWidth: width
+
+ ScrollBar.vertical : ScrollBar {
+ visible: parent.contentHeight > parent.height
+ width: 20
+ background: Rectangle {
+ color: hifi.colors.tableScrollBackgroundDark
+ }
+ }
+
+ Text {
+ id: header
+ color: "#ffffff"
+ text: "Choose an Entity Type to Create:"
+ font.pixelSize: 14
+ font.bold: true
+ anchors.top: parent.top
+ anchors.topMargin: 28
+ anchors.left: parent.left
+ anchors.leftMargin: 28
+ }
+
+ Flow {
+ id: createEntitiesFlow
+ spacing: 35
+ anchors.right: parent.right
+ anchors.rightMargin: 55
+ anchors.left: parent.left
+ anchors.leftMargin: 55
+ anchors.top: parent.top
+ anchors.topMargin: 70
+
+
+ NewEntityButton {
+ icon: "icons/create-icons/94-model-01.svg"
+ text: "MODEL"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newModelButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/21-cube-01.svg"
+ text: "CUBE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newCubeButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/22-sphere-01.svg"
+ text: "SPHERE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newSphereButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/24-light-01.svg"
+ text: "LIGHT"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newLightButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/20-text-01.svg"
+ text: "TEXT"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newTextButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/image.svg"
+ text: "IMAGE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newImageButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/25-web-1-01.svg"
+ text: "WEB"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newWebButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/23-zone-01.svg"
+ text: "ZONE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newZoneButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/90-particles-01.svg"
+ text: "PARTICLE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newParticleButton" }
+ });
+ editTabView.currentIndex = tabIndex.particle
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/126-material-01.svg"
+ text: "MATERIAL"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "newMaterialButton" }
+ });
+ editTabView.currentIndex = tabIndex.properties
+ }
+ }
+ }
+
+ HifiControls.Button {
+ id: assetServerButton
+ text: "Open This Domain's Asset Server"
+ color: hifi.buttons.black
+ colorScheme: hifi.colorSchemes.dark
+ anchors.right: parent.right
+ anchors.rightMargin: 55
+ anchors.left: parent.left
+ anchors.leftMargin: 55
+ anchors.top: createEntitiesFlow.bottom
+ anchors.topMargin: 35
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "openAssetBrowserButton" }
+ });
+ }
+ }
+
+ HifiControls.Button {
+ id: importButton
+ text: "Import Entities (.json)"
+ color: hifi.buttons.black
+ colorScheme: hifi.colorSchemes.dark
+ anchors.right: parent.right
+ anchors.rightMargin: 55
+ anchors.left: parent.left
+ anchors.leftMargin: 55
+ anchors.top: assetServerButton.bottom
+ anchors.topMargin: 20
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked",
+ params: { buttonName: "importEntitiesButton" }
+ });
+ }
+ }
+ }
+ } // Flickable
+ }
+ }
+
+ EditTabButton {
+ title: "PROPERTIES"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ property Component visualItem: Component {
+ WebView {
+ id: entityPropertiesWebView
+ url: Paths.defaultScripts + "/system/html/entityProperties.html"
+ enabled: true
+ }
+ }
+ }
+
+ EditTabButton {
+ title: "GRID"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ property Component visualItem: Component {
+ WebView {
+ id: gridControlsWebView
+ url: Paths.defaultScripts + "/system/html/gridControls.html"
+ enabled: true
+ }
+ }
+ }
+
+ EditTabButton {
+ title: "P"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ property Component visualItem: Component {
+ WebView {
+ id: particleExplorerWebView
+ url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html"
+ enabled: true
+ }
+ }
+ }
+
+ function fromScript(message) {
+ switch (message.method) {
+ case 'selectTab':
+ selectTab(message.params.id);
+ break;
+ default:
+ console.warn('Unrecognized message:', JSON.stringify(message));
+ }
+ }
+
+ // Changes the current tab based on tab index or title as input
+ function selectTab(id) {
+ if (typeof id === 'number') {
+ if (id >= tabIndex.create && id <= tabIndex.particle) {
+ editTabView.currentIndex = id;
+ } else {
+ console.warn('Attempt to switch to invalid tab:', id);
+ }
+ } else if (typeof id === 'string'){
+ switch (id.toLowerCase()) {
+ case 'create':
+ editTabView.currentIndex = tabIndex.create;
+ break;
+ case 'properties':
+ editTabView.currentIndex = tabIndex.properties;
+ break;
+ case 'grid':
+ editTabView.currentIndex = tabIndex.grid;
+ break;
+ case 'particle':
+ editTabView.currentIndex = tabIndex.particle;
+ break;
+ default:
+ console.warn('Attempt to switch to invalid tab:', id);
+ }
+ } else {
+ console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id));
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/EntityList.qml b/interface/resources/qml/hifi/tablet/EntityList.qml
new file mode 100644
index 0000000000..f4b47c19bb
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/EntityList.qml
@@ -0,0 +1,5 @@
+WebView {
+ id: entityListToolWebView
+ url: Paths.defaultScripts + "/system/html/entityList.html"
+ enabled: true
+}
diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml
index 6df97e67b0..526a42f8e2 100644
--- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml
+++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml
@@ -29,12 +29,16 @@ Rectangle {
property bool keyboardRasied: false
function errorMessageBox(message) {
- return desktop.messageBox({
- icon: hifi.icons.warning,
- defaultButton: OriginalDialogs.StandardButton.Ok,
- title: "Error",
- text: message
- });
+ try {
+ return desktop.messageBox({
+ icon: hifi.icons.warning,
+ defaultButton: OriginalDialogs.StandardButton.Ok,
+ title: "Error",
+ text: message
+ });
+ } catch(e) {
+ Window.alert(message);
+ }
}
Item {
diff --git a/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml b/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml
new file mode 100644
index 0000000000..def816c36e
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml
@@ -0,0 +1,20 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+StackView {
+ id: stackView
+ anchors.fill: parent
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+ anchors.topMargin: 40
+
+ signal sendToScript(var message);
+
+ NewMaterialDialog {
+ id: dialog
+ anchors.fill: parent
+ Component.onCompleted:{
+ dialog.sendToScript.connect(stackView.sendToScript);
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml
index 8f6718e1f3..10b844c987 100644
--- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml
+++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml
@@ -29,12 +29,16 @@ Rectangle {
property bool keyboardRasied: false
function errorMessageBox(message) {
- return desktop.messageBox({
- icon: hifi.icons.warning,
- defaultButton: OriginalDialogs.StandardButton.Ok,
- title: "Error",
- text: message
- });
+ try {
+ return desktop.messageBox({
+ icon: hifi.icons.warning,
+ defaultButton: OriginalDialogs.StandardButton.Ok,
+ title: "Error",
+ text: message
+ });
+ } catch(e) {
+ Window.alert(message);
+ }
}
Item {
diff --git a/interface/resources/qml/hifi/tablet/NewModelWindow.qml b/interface/resources/qml/hifi/tablet/NewModelWindow.qml
new file mode 100644
index 0000000000..616a44ab7a
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/NewModelWindow.qml
@@ -0,0 +1,20 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+StackView {
+ id: stackView
+ anchors.fill: parent
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+ anchors.topMargin: 40
+
+ signal sendToScript(var message);
+
+ NewModelDialog {
+ id: dialog
+ anchors.fill: parent
+ Component.onCompleted:{
+ dialog.sendToScript.connect(stackView.sendToScript);
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml
index 20682372c5..c2aff08e35 100644
--- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml
+++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml
@@ -28,6 +28,7 @@ Flickable {
onPluginNameChanged: {
if (page !== null) {
page.pluginName = flick.pluginName;
+ page.displayConfiguration();
}
}
diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
index 08f86770e6..9a472de046 100644
--- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
+++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
@@ -89,6 +89,7 @@ StackView {
property bool keyboardEnabled: false
property bool punctuationMode: false
+ property bool keyboardRaised: false
width: parent.width
height: parent.height
@@ -210,6 +211,8 @@ StackView {
QQC2.TextField {
id: addressLine
+
+ focus: true
width: addressLineContainer.width - addressLineContainer.anchors.leftMargin - addressLineContainer.anchors.rightMargin;
anchors {
left: addressLineContainer.left;
@@ -236,24 +239,20 @@ StackView {
color: hifi.colors.text
background: Item {}
- QQC2.Label {
- T.TextField {
- id: control
+ }
- padding: 6 // numbers taken from Qt\5.9.2\Src\qtquickcontrols2\src\imports\controls\TextField.qml
- leftPadding: padding + 4
- }
+ QQC2.Label {
+ font: addressLine.font
- font: parent.font
+ x: addressLine.x
+ y: addressLine.y
+ leftPadding: addressLine.leftPadding
+ topPadding: addressLine.topPadding
- x: control.leftPadding
- y: control.topPadding
-
- text: parent.placeholderText2
- verticalAlignment: "AlignVCenter"
- color: 'gray'
- visible: parent.text === ''
- }
+ text: addressLine.placeholderText2
+ verticalAlignment: "AlignVCenter"
+ color: 'gray'
+ visible: addressLine.text === ''
}
Rectangle {
diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml
index 63801019b9..4f1100f20b 100644
--- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml
+++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml
@@ -32,6 +32,6 @@ StackView {
TabletPreferencesDialog {
id: root
objectName: "TabletGeneralPreferences"
- showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"]
+ showCategories: ["User Interface", "Mouse Sensitivity", "HMD", "Snapshots", "Privacy"]
}
}
diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
index 646a0d2c77..05f45dc61e 100644
--- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
+++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
@@ -24,6 +24,7 @@ Item {
HifiConstants { id: hifi }
property var sections: []
property var showCategories: []
+ property var categoryProperties: ({})
property bool keyboardEnabled: false
property bool keyboardRaised: false
@@ -100,7 +101,8 @@ Item {
// NOTE: the sort order of items in the showCategories array is the same order in the dialog.
for (i = 0; i < showCategories.length; i++) {
if (categoryMap[showCategories[i]]) {
- sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i]}));
+ var properties = categoryProperties.hasOwnProperty(showCategories[i]) ? categoryProperties[showCategories[i]] : {};
+ sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i], sectionProperties: properties}));
}
}
diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml
index 15db58decc..833175f311 100644
--- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml
+++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml
@@ -24,6 +24,7 @@ Preference {
property bool isLast: false
property string name: "Header"
property real spacing: 8
+ property var sectionProperties: ({})
default property alias preferences: contentContainer.children
HifiConstants { id: hifi }
@@ -163,6 +164,28 @@ Preference {
if (builder) {
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) , z: zpos}));
+
+ var preferenceObject = preferences[preferences.length - 1];
+ var props = sectionProperties.hasOwnProperty(preference.name) ? sectionProperties[preference.name] : {};
+
+ for(var prop in props) {
+ var value = props[prop];
+ if(value.indexOf('.') !== -1) {
+ var splittedValues = value.split('.');
+ if(splittedValues[0] === 'parent') {
+ value = preferenceObject.parent[splittedValues[1]];
+ }
+ } else if(value === 'undefined') {
+ value = undefined;
+ }
+
+ if(prop.indexOf('.') !== -1) {
+ var splittedProps = prop.split('.');
+ preferenceObject[splittedProps[0]][splittedProps[1]] = value;
+ } else {
+ preferenceObject[prop] = value;
+ }
+ }
}
}
}
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index ef7319e63f..419382f2cb 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -10,35 +10,21 @@
//
#include "AndroidHelper.h"
#include
-#include
+#include "Application.h"
+
+#if defined(qApp)
+#undef qApp
+#endif
+#define qApp (static_cast(QCoreApplication::instance()))
AndroidHelper::AndroidHelper() {
}
AndroidHelper::~AndroidHelper() {
- workerThread.quit();
- workerThread.wait();
}
-void AndroidHelper::init() {
- workerThread.start();
- _accountManager = QSharedPointer(new AccountManager, &QObject::deleteLater);
- _accountManager->setIsAgent(true);
- _accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
- _accountManager->setSessionID(DependencyManager::get()->getSessionID());
- connect(_accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
- DependencyManager::get()->setAccountInfo(AndroidHelper::instance().getAccountManager()->getAccountInfo());
- DependencyManager::get()->setAuthURL(authURL);
- });
-
- connect(_accountManager.data(), &AccountManager::logoutComplete, [] () {
- DependencyManager::get()->logout();
- });
- _accountManager->moveToThread(&workerThread);
-}
-
-void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene) {
- emit androidActivityRequested(activityName, backToScene);
+void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene, QList args) {
+ emit androidActivityRequested(activityName, backToScene, args);
}
void AndroidHelper::notifyLoadComplete() {
@@ -49,6 +35,10 @@ void AndroidHelper::notifyEnterForeground() {
emit enterForeground();
}
+void AndroidHelper::notifyBeforeEnterBackground() {
+ emit beforeEnterBackground();
+}
+
void AndroidHelper::notifyEnterBackground() {
emit enterBackground();
}
@@ -60,3 +50,9 @@ void AndroidHelper::performHapticFeedback(int duration) {
void AndroidHelper::showLoginDialog() {
emit androidActivityRequested("Login", true);
}
+
+void AndroidHelper::processURL(const QString &url) {
+ if (qApp->canAcceptURL(url)) {
+ qApp->acceptURL(url);
+ }
+}
diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h
index 007c0db4a5..03d92f91d9 100644
--- a/interface/src/AndroidHelper.h
+++ b/interface/src/AndroidHelper.h
@@ -13,8 +13,6 @@
#define hifi_Android_Helper_h
#include
-#include
-#include
class AndroidHelper : public QObject {
Q_OBJECT
@@ -23,15 +21,15 @@ public:
static AndroidHelper instance;
return instance;
}
- void init();
- void requestActivity(const QString &activityName, const bool backToScene);
+ void requestActivity(const QString &activityName, const bool backToScene, QList args = QList());
void notifyLoadComplete();
void notifyEnterForeground();
+ void notifyBeforeEnterBackground();
void notifyEnterBackground();
void performHapticFeedback(int duration);
+ void processURL(const QString &url);
- QSharedPointer getAccountManager() { return _accountManager; }
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
@@ -39,9 +37,10 @@ public slots:
void showLoginDialog();
signals:
- void androidActivityRequested(const QString &activityName, const bool backToScene);
+ void androidActivityRequested(const QString &activityName, const bool backToScene, QList args = QList());
void qtAppLoadComplete();
void enterForeground();
+ void beforeEnterBackground();
void enterBackground();
void hapticFeedbackRequested(int duration);
@@ -49,8 +48,6 @@ signals:
private:
AndroidHelper();
~AndroidHelper();
- QSharedPointer _accountManager;
- QThread workerThread;
};
#endif
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index cffed61b37..b322b823cd 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -30,6 +30,7 @@
#include
#include
+#include
#include
#include
#include
@@ -129,6 +130,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -269,9 +271,6 @@ public:
}
_renderContext->doneCurrent();
- // Deleting the object with automatically shutdown the thread
- connect(qApp, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
-
// Transfer to a new thread
moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) {
hifi::qt::addBlockingForbiddenThread("Render", renderThread);
@@ -814,6 +813,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
}
// Tell the plugin manager about our statically linked plugins
+ DependencyManager::set();
auto pluginManager = PluginManager::getInstance();
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
pluginManager->setDisplayPluginProvider([] { return getDisplayPlugins(); });
@@ -1074,6 +1074,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf");
_window->setWindowTitle("High Fidelity Interface");
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
@@ -1081,6 +1082,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto nodeList = DependencyManager::get();
nodeList->startThread();
+ // move the AddressManager to the NodeList thread so that domain resets due to domain changes always occur
+ // before we tell MyAvatar to go to a new location in the new domain
+ auto addressManager = DependencyManager::get();
+ addressManager->moveToThread(nodeList->thread());
+
const char** constArgv = const_cast(argv);
if (cmdOptionExists(argc, constArgv, "--disableWatchdog")) {
DISABLE_WATCHDOG = true;
@@ -1231,8 +1237,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
accountManager->setIsAgent(true);
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
- auto addressManager = DependencyManager::get();
-
// use our MyAvatar position and quat for address manager path
addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldPosition(); });
addressManager->setOrientationGetter([this]{ return getMyAvatar()->getWorldOrientation(); });
@@ -1375,6 +1379,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
initializeRenderEngine();
qCDebug(interfaceapp, "Initialized Render Engine.");
+ // Overlays need to exist before we set the ContextOverlayInterface dependency
+ _overlays.init(); // do this before scripts load
+ DependencyManager::set();
+
// Initialize the user interface and menu system
// Needs to happen AFTER the render engine initialization to access its configuration
initializeUi();
@@ -1440,8 +1448,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// add firstRun flag from settings to launch event
Setting::Handle firstRun { Settings::firstRun, true };
- QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
-
auto& userActivityLogger = UserActivityLogger::getInstance();
if (userActivityLogger.isEnabled()) {
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
@@ -1493,13 +1499,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["first_run"] = firstRun.get();
// add the user's machine ID to the launch event
+ QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
properties["machine_fingerprint"] = machineFingerPrint;
userActivityLogger.logAction("launch", properties);
}
- setCrashAnnotation("machine_fingerprint", machineFingerPrint.toStdString());
-
_entityEditSender.setMyAvatar(myAvatar.get());
// The entity octree will have to know about MyAvatar for the parentJointName import
@@ -1511,10 +1516,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// allow you to move an entity around in your hand
_entityEditSender.setPacketsPerSecond(3000); // super high!!
- // Overlays need to exist before we set the ContextOverlayInterface dependency
- _overlays.init(); // do this before scripts load
- DependencyManager::set();
-
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
@@ -2119,6 +2120,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|| ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose));
lastLeftHandPose = leftHandPose;
lastRightHandPose = rightHandPose;
+ properties["avatar_identity_requests_sent"] = DependencyManager::get()->getIdentityRequestsSent();
UserActivityLogger::getInstance().logAction("stats", properties);
});
@@ -2256,7 +2258,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
#if defined(Q_OS_ANDROID)
- AndroidHelper::instance().init();
+ connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground);
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
AndroidHelper::instance().notifyLoadComplete();
@@ -2553,25 +2555,28 @@ Application::~Application() {
_octreeProcessor.terminate();
_entityEditSender.terminate();
+ if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
+ steamClient->shutdown();
+ }
+ DependencyManager::destroy();
+
+ DependencyManager::destroy(); // must be destroyed before the FramebufferCache
+
DependencyManager::destroy();
DependencyManager::destroy();
DependencyManager::destroy();
DependencyManager::destroy();
DependencyManager::destroy();
- DependencyManager::destroy();
DependencyManager::destroy();
DependencyManager::destroy();
DependencyManager::destroy();
+ DependencyManager::destroy();
DependencyManager::get()->cleanup();
// remove the NodeList from the DependencyManager
DependencyManager::destroy();
- if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
- steamClient->shutdown();
- }
-
#if 0
ConnexionClient::getInstance().destroy();
#endif
@@ -2591,6 +2596,8 @@ Application::~Application() {
// Can't log to file passed this point, FileLogger about to be deleted
qInstallMessageHandler(LogHandler::verboseMessageHandler);
+
+ _renderEventHandler->deleteLater();
}
void Application::initializeGL() {
@@ -2717,7 +2724,7 @@ void Application::initializeDisplayPlugins() {
setDisplayPlugin(defaultDisplayPlugin);
// Now set the desired plugin if it's not the same as the default plugin
- if (targetDisplayPlugin != defaultDisplayPlugin) {
+ if (targetDisplayPlugin && (targetDisplayPlugin != defaultDisplayPlugin)) {
setDisplayPlugin(targetDisplayPlugin);
}
@@ -2891,6 +2898,7 @@ void Application::initializeUi() {
auto compositorHelper = DependencyManager::get();
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] {
if (isHMDMode()) {
+ auto compositorHelper = DependencyManager::get(); // don't capture outer smartpointer
showCursor(compositorHelper->getAllowMouseCapture() ?
Cursor::Manager::lookupIcon(_preferredCursor.get()) :
Cursor::Icon::SYSTEM);
@@ -2973,6 +2981,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("Overlays", &_overlays);
surfaceContext->setContextProperty("Window", DependencyManager::get().data());
+ surfaceContext->setContextProperty("Desktop", DependencyManager::get().data());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data());
@@ -3212,6 +3221,7 @@ void Application::setSettingConstrainToolbarPosition(bool setting) {
void Application::showHelp() {
static const QString HAND_CONTROLLER_NAME_VIVE = "vive";
static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus";
+ static const QString HAND_CONTROLLER_NAME_WINDOWS_MR = "windowsMR";
static const QString TAB_KEYBOARD_MOUSE = "kbm";
static const QString TAB_GAMEPAD = "gamepad";
@@ -3226,9 +3236,13 @@ void Application::showHelp() {
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
defaultTab = TAB_HAND_CONTROLLERS;
handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH;
+ } else if (qApp->getActiveDisplayPlugin()->getName() == "WindowMS") {
+ defaultTab = TAB_HAND_CONTROLLERS;
+ handControllerName = HAND_CONTROLLER_NAME_WINDOWS_MR;
} else if (PluginUtils::isXboxControllerAvailable()) {
defaultTab = TAB_GAMEPAD;
}
+ // TODO need some way to detect windowsMR to load controls reference default tab in Help > Controls Reference menu.
QUrlQuery queryString;
queryString.addQueryItem("handControllerName", handControllerName);
@@ -3254,13 +3268,22 @@ void Application::resizeGL() {
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
// Otherwise, it must rebuild the FBOs
uvec2 framebufferSize = displayPlugin->getRecommendedRenderSize();
- float renderResolutionScale = getRenderResolutionScale();
- uvec2 renderSize = uvec2(vec2(framebufferSize) * renderResolutionScale);
+ uvec2 renderSize = uvec2(framebufferSize);
if (_renderResolution != renderSize) {
_renderResolution = renderSize;
DependencyManager::get()->setFrameBufferSize(fromGlm(renderSize));
}
+ auto renderResolutionScale = getRenderResolutionScale();
+ if (displayPlugin->getRenderResolutionScale() != renderResolutionScale) {
+ auto renderConfig = _renderEngine->getConfiguration();
+ assert(renderConfig);
+ auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask");
+ assert(mainView);
+ mainView->setProperty("resolutionScale", renderResolutionScale);
+ displayPlugin->setRenderResolutionScale(renderResolutionScale);
+ }
+
// FIXME the aspect ratio for stereo displays is incorrect based on this.
float aspectRatio = displayPlugin->getRecommendedAspectRatio();
_myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio,
@@ -3272,7 +3295,6 @@ void Application::resizeGL() {
}
DependencyManager::get()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
- displayPlugin->setRenderResolutionScale(renderResolutionScale);
}
void Application::handleSandboxStatus(QNetworkReply* reply) {
@@ -4013,7 +4035,18 @@ void Application::mousePressEvent(QMouseEvent* event) {
return;
}
+#if defined(Q_OS_MAC)
+ // Fix for OSX right click dragging on window when coming from a native window
+ bool isFocussed = hasFocus();
+ if (!isFocussed && event->button() == Qt::MouseButton::RightButton) {
+ setFocus();
+ isFocussed = true;
+ }
+
+ if (isFocussed) {
+#else
if (hasFocus()) {
+#endif
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->mousePressEvent(event);
}
@@ -6630,6 +6663,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
+ registerInteractiveWindowMetaType(scriptEngine.data());
+
DependencyManager::get()->registerMetaTypes(scriptEngine.data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
@@ -6977,7 +7012,9 @@ void Application::showAssetServerWidget(QString filePath) {
DependencyManager::get()->show(url, "AssetServer", startUpload);
} else {
static const QUrl url("hifi/dialogs/TabletAssetServer.qml");
- tablet->pushOntoStack(url);
+ if (!tablet->isPathLoaded(url)) {
+ tablet->pushOntoStack(url);
+ }
}
}
@@ -7614,7 +7651,6 @@ void Application::toggleEntityScriptServerLogDialog() {
void Application::loadAddAvatarBookmarkDialog() const {
auto avatarBookmarks = DependencyManager::get();
- avatarBookmarks->addBookmark();
}
void Application::loadAvatarBrowser() const {
@@ -8296,7 +8332,24 @@ void Application::saveNextPhysicsStats(QString filename) {
_physicsEngine->saveNextPhysicsStats(filename);
}
+void Application::copyToClipboard(const QString& text) {
+ if (QThread::currentThread() != qApp->thread()) {
+ QMetaObject::invokeMethod(this, "copyToClipboard");
+ return;
+ }
+
+ // assume that the address is being copied because the user wants a shareable address
+ QApplication::clipboard()->setText(text);
+}
+
#if defined(Q_OS_ANDROID)
+void Application::beforeEnterBackground() {
+ auto nodeList = DependencyManager::get();
+ nodeList->setSendDomainServerCheckInEnabled(false);
+ nodeList->reset(true);
+ clearDomainOctreeDetails();
+}
+
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get().data(),
"stop", Qt::BlockingQueuedConnection);
@@ -8311,6 +8364,8 @@ void Application::enterForeground() {
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
qWarning() << "Could not re-activate display plugin";
}
+ auto nodeList = DependencyManager::get();
+ nodeList->setSendDomainServerCheckInEnabled(true);
}
#endif
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 346ea258da..94e561e550 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -310,7 +310,10 @@ public:
void loadAvatarScripts(const QVector& urls);
void unloadAvatarScripts();
+ Q_INVOKABLE void copyToClipboard(const QString& text);
+
#if defined(Q_OS_ANDROID)
+ void beforeEnterBackground();
void enterBackground();
void enterForeground();
#endif
diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp
index 2daa49dcf7..6648fa2eb7 100644
--- a/interface/src/Application_render.cpp
+++ b/interface/src/Application_render.cpp
@@ -102,7 +102,7 @@ void Application::paintGL() {
PerformanceTimer perfTimer("renderOverlay");
// NOTE: There is no batch associated with this renderArgs
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
- renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize() * getRenderResolutionScale());
+ renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize());
_applicationOverlay.renderOverlay(&renderArgs);
}
@@ -118,7 +118,7 @@ void Application::paintGL() {
// Primary rendering pass
auto framebufferCache = DependencyManager::get();
finalFramebufferSize = framebufferCache->getFrameBufferSize();
- // Final framebuffer that will be handled to the display-plugin
+ // Final framebuffer that will be handed to the display-plugin
finalFramebuffer = framebufferCache->getFramebuffer();
}
@@ -139,7 +139,10 @@ void Application::paintGL() {
frame->frameIndex = _renderFrameCount;
frame->framebuffer = finalFramebuffer;
frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) {
- DependencyManager::get()->releaseFramebuffer(framebuffer);
+ auto frameBufferCache = DependencyManager::get();
+ if (frameBufferCache) {
+ frameBufferCache->releaseFramebuffer(framebuffer);
+ }
};
// deliver final scene rendering commands to the display plugin
{
diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp
index f97c02bca3..4e3e539dea 100644
--- a/interface/src/AvatarBookmarks.cpp
+++ b/interface/src/AvatarBookmarks.cpp
@@ -17,7 +17,9 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -28,7 +30,6 @@
#include
#include "MainWindow.h"
-#include "Menu.h"
#include "InterfaceLogging.h"
#include "QVariantGLM.h"
@@ -92,10 +93,96 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
}
AvatarBookmarks::AvatarBookmarks() {
+ QDir directory(PathUtils::getAppDataPath());
+ if (!directory.exists()) {
+ directory.mkpath(".");
+ }
+
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
+ if(!QFile::exists(_bookmarksFilename)) {
+ auto defaultBookmarksFilename = PathUtils::resourcesPath() + QString("avatar/bookmarks") + "/" + AVATARBOOKMARKS_FILENAME;
+ if (!QFile::exists(defaultBookmarksFilename)) {
+ qDebug() << defaultBookmarksFilename << "doesn't exist!";
+ }
+
+ if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) {
+ qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename;
+ }
+ }
readFromFile();
}
+void AvatarBookmarks::addBookmark(const QString& bookmarkName) {
+ if (QThread::currentThread() != thread()) {
+ BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_ARG(QString, bookmarkName));
+ return;
+ }
+ QVariantMap bookmark = getAvatarDataToBookmark();
+ insert(bookmarkName, bookmark);
+
+ emit bookmarkAdded(bookmarkName);
+}
+
+void AvatarBookmarks::saveBookmark(const QString& bookmarkName) {
+ if (QThread::currentThread() != thread()) {
+ BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_ARG(QString, bookmarkName));
+ return;
+ }
+ if (contains(bookmarkName)) {
+ QVariantMap bookmark = getAvatarDataToBookmark();
+ insert(bookmarkName, bookmark);
+ }
+}
+
+void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
+ if (QThread::currentThread() != thread()) {
+ BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_ARG(QString, bookmarkName));
+ return;
+ }
+
+ remove(bookmarkName);
+ emit bookmarkDeleted(bookmarkName);
+}
+
+void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
+ auto myAvatar = DependencyManager::get()->getMyAvatar();
+ myAvatar->removeAvatarEntities();
+
+ addAvatarEntities(avatarEntities);
+}
+
+void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
+ if (QThread::currentThread() != thread()) {
+ BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName));
+ return;
+ }
+
+ auto bookmarkEntry = _bookmarks.find(bookmarkName);
+
+ if (bookmarkEntry != _bookmarks.end()) {
+ QVariantMap bookmark = bookmarkEntry.value().toMap();
+ if (!bookmark.empty()) {
+ auto myAvatar = DependencyManager::get()->getMyAvatar();
+ myAvatar->removeAvatarEntities();
+ const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
+ myAvatar->useFullAvatarURL(avatarUrl);
+ qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
+ const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList();
+
+ qCDebug(interfaceapp) << "Attach " << attachments;
+ myAvatar->setAttachmentsVariant(attachments);
+
+ const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
+ myAvatar->setAvatarScale(qScale);
+
+ const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
+ addAvatarEntities(avatarEntities);
+
+ emit bookmarkLoaded(bookmarkName);
+ }
+ }
+}
+
void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
@@ -115,99 +202,37 @@ void AvatarBookmarks::readFromFile() {
Bookmarks::readFromFile();
}
-void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
- // Add menus/actions
- auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);
- QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
- _bookmarksMenu = menu->addMenu(MenuOption::AvatarBookmarks);
- _deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarBookmark);
- QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
-
- for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
- addBookmarkToMenu(menubar, it.key(), it.value());
+QVariantMap AvatarBookmarks::getBookmark(const QString &bookmarkName)
+{
+ if (QThread::currentThread() != thread()) {
+ QVariantMap result;
+ BLOCKING_INVOKE_METHOD(this, "getBookmark", Q_RETURN_ARG(QVariantMap, result), Q_ARG(QString, bookmarkName));
+ return result;
}
- Bookmarks::sortActions(menubar, _bookmarksMenu);
+ QVariantMap bookmark;
+
+ auto bookmarkEntry = _bookmarks.find(bookmarkName);
+
+ if (bookmarkEntry != _bookmarks.end()) {
+ bookmark = bookmarkEntry.value().toMap();
+ }
+
+ return bookmark;
}
-void AvatarBookmarks::changeToBookmarkedAvatar() {
- QAction* action = qobject_cast(sender());
+QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
auto myAvatar = DependencyManager::get()->getMyAvatar();
+ const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
+ const QVariant& avatarScale = myAvatar->getAvatarScale();
-
- if (action->data().type() == QVariant::String) {
- // TODO: Phase this out eventually.
- // Legacy avatar bookmark.
-
- myAvatar->useFullAvatarURL(action->data().toString());
- qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark ";
- } else {
-
- const QMap bookmark = action->data().toMap();
- // Not magic value. This is the current made version, and if it changes this interpreter should be updated to
- // handle the new one separately.
- // This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous
- if (bookmark.value(ENTRY_VERSION) == 3) {
- myAvatar->removeAvatarEntities();
- const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
- myAvatar->useFullAvatarURL(avatarUrl);
- qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
- const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList();
-
- qCDebug(interfaceapp) << "Attach " << attachments;
- myAvatar->setAttachmentsVariant(attachments);
-
- const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
- myAvatar->setAvatarScale(qScale);
-
- const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
- addAvatarEntities(avatarEntities);
-
- } else {
- qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark";
- }
- }
-
-}
-
-void AvatarBookmarks::addBookmark() {
- ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString());
- connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
- disconnect(dlg, &ModalDialogListener::response, this, nullptr);
- auto bookmarkName = response.toString();
- bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
- if (bookmarkName.length() == 0) {
- return;
- }
-
- auto myAvatar = DependencyManager::get()->getMyAvatar();
-
- const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
- const QVariant& avatarScale = myAvatar->getAvatarScale();
-
- // If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
- QVariantMap bookmark;
- bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
- bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
- bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
- bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
- bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
-
- Bookmarks::addBookmarkToFile(bookmarkName, bookmark);
- });
-
-}
-
-void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
- QAction* changeAction = _bookmarksMenu->newAction();
- changeAction->setData(bookmark);
- connect(changeAction, SIGNAL(triggered()), this, SLOT(changeToBookmarkedAvatar()));
- if (!_isMenuSorted) {
- menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
- } else {
- // TODO: this is aggressive but other alternatives have proved less fruitful so far.
- menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
- Bookmarks::sortActions(menubar, _bookmarksMenu);
- }
+ // If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
+ QVariantMap bookmark;
+ bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
+ bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
+ bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
+ bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
+ bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
+ return bookmark;
}
diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h
index 7b47ea8af7..f1bc6820eb 100644
--- a/interface/src/AvatarBookmarks.h
+++ b/interface/src/AvatarBookmarks.h
@@ -30,19 +30,50 @@ class AvatarBookmarks: public Bookmarks, public Dependency {
public:
AvatarBookmarks();
- void setupMenus(Menu* menubar, MenuWrapper* menu) override;
-
+ void setupMenus(Menu* menubar, MenuWrapper* menu) override {};
+ Q_INVOKABLE QVariantMap getBookmark(const QString& bookmarkName);
public slots:
/**jsdoc
* Add the current Avatar to your avatar bookmarks.
* @function AvatarBookmarks.addBookMark
*/
- void addBookmark();
+ void addBookmark(const QString& bookmarkName);
+ void saveBookmark(const QString& bookmarkName);
+ void loadBookmark(const QString& bookmarkName);
+ void removeBookmark(const QString& bookmarkName);
+ void updateAvatarEntities(const QVariantList& avatarEntities);
+ QVariantMap getBookmarks() { return _bookmarks; }
+
+signals:
+ /**jsdoc
+ * This function gets triggered after avatar loaded from bookmark
+ * @function AvatarBookmarks.bookmarkLoaded
+ * @param {string} bookmarkName
+ * @returns {Signal}
+ */
+ void bookmarkLoaded(const QString& bookmarkName);
+
+ /**jsdoc
+ * This function gets triggered after avatar bookmark deleted
+ * @function AvatarBookmarks.bookmarkDeleted
+ * @param {string} bookmarkName
+ * @returns {Signal}
+ */
+ void bookmarkDeleted(const QString& bookmarkName);
+
+ /**jsdoc
+ * This function gets triggered after avatar bookmark added
+ * @function AvatarBookmarks.bookmarkAdded
+ * @param {string} bookmarkName
+ * @returns {Signal}
+ */
+ void bookmarkAdded(const QString& bookmarkName);
protected:
- void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
+ void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override {};
void readFromFile() override;
+ QVariantMap getAvatarDataToBookmark();
private:
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
@@ -53,9 +84,6 @@ private:
const QString ENTRY_VERSION = "version";
const int AVATAR_BOOKMARK_VERSION = 3;
-
-private slots:
- void changeToBookmarkedAvatar();
};
#endif // hifi_AvatarBookmarks_h
diff --git a/interface/src/Bookmarks.cpp b/interface/src/Bookmarks.cpp
index 6e99b81e50..9a8d8eb279 100644
--- a/interface/src/Bookmarks.cpp
+++ b/interface/src/Bookmarks.cpp
@@ -46,6 +46,10 @@ void Bookmarks::deleteBookmark() {
return;
}
+ deleteBookmark(bookmarkName);
+}
+
+void Bookmarks::deleteBookmark(const QString& bookmarkName) {
removeBookmarkFromMenu(Menu::getInstance(), bookmarkName);
remove(bookmarkName);
diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h
index dc08d4b279..88510e4eda 100644
--- a/interface/src/Bookmarks.h
+++ b/interface/src/Bookmarks.h
@@ -31,6 +31,8 @@ public:
QString addressForBookmark(const QString& name) const;
protected:
+ void deleteBookmark(const QString& bookmarkName);
+
void addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark);
virtual void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) = 0;
void enableMenuItems(bool enabled);
@@ -38,9 +40,10 @@ protected:
void insert(const QString& name, const QVariant& address); // Overwrites any existing entry with same name.
void sortActions(Menu* menubar, MenuWrapper* menu);
int getMenuItemLocation(QList actions, const QString& name) const;
-
+ void removeBookmarkFromMenu(Menu* menubar, const QString& name);
bool contains(const QString& name) const;
-
+ void remove(const QString& name);
+
QVariantMap _bookmarks; // { name: url, ... }
QPointer _bookmarksMenu;
QPointer _deleteBookmarksAction;
@@ -57,12 +60,9 @@ protected slots:
void deleteBookmark();
private:
- void remove(const QString& name);
static bool sortOrder(QAction* a, QAction* b);
void persistToFile();
-
- void removeBookmarkFromMenu(Menu* menubar, const QString& name);
};
#endif // hifi_Bookmarks_h
diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h
index 4a6483c700..6f8e9c3bf6 100644
--- a/interface/src/CrashHandler.h
+++ b/interface/src/CrashHandler.h
@@ -14,8 +14,7 @@
#include
-bool startCrashHandler();
+bool startCrashHandler(std::string appPath);
void setCrashAnnotation(std::string name, std::string value);
-
-#endif
\ No newline at end of file
+#endif // hifi_CrashHandler_h
diff --git a/interface/src/CrashHandler_Breakpad.cpp b/interface/src/CrashHandler_Breakpad.cpp
index f2a174b6ea..c21bfa95e0 100644
--- a/interface/src/CrashHandler_Breakpad.cpp
+++ b/interface/src/CrashHandler_Breakpad.cpp
@@ -9,10 +9,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-#include "CrashHandler.h"
-
#if HAS_BREAKPAD
+#include "CrashHandler.h"
+
#include
#include
@@ -23,8 +23,10 @@
#include
#include
-#include
#include
+#include
+#include
+#include
google_breakpad::ExceptionHandler* gBreakpadHandler;
@@ -55,11 +57,14 @@ void flushAnnotations() {
settings.sync();
}
-bool startCrashHandler() {
+bool startCrashHandler(std::string appPath) {
annotations["version"] = BuildInfo::VERSION;
annotations["build_number"] = BuildInfo::BUILD_NUMBER;
annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING;
+ auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
+ annotations["machine_fingerprint"] = machineFingerPrint;
+
flushAnnotations();
gBreakpadHandler = new google_breakpad::ExceptionHandler(
diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp
index 76d4a8e2e1..d1b5103990 100644
--- a/interface/src/CrashHandler_Crashpad.cpp
+++ b/interface/src/CrashHandler_Crashpad.cpp
@@ -9,21 +9,24 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#if HAS_CRASHPAD
+
#include "CrashHandler.h"
#include
-#include
-
-#if HAS_CRASHPAD
-
#include
#include
-#include
+#include
#include
+#include
-#include
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++14-extensions"
+#endif
#include
#include
@@ -31,19 +34,32 @@
#include
#include
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
#include
+#include
+#include
using namespace crashpad;
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
-extern QString qAppFileName();
-
CrashpadClient* client { nullptr };
std::mutex annotationMutex;
crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr };
+#if defined(Q_OS_WIN)
+static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler.exe" };
+#else
+static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" };
+#endif
+
+#ifdef Q_OS_WIN
+#include
+
LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
if (!client) {
return EXCEPTION_CONTINUE_SEARCH;
@@ -56,8 +72,9 @@ LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) {
return EXCEPTION_CONTINUE_SEARCH;
}
+#endif
-bool startCrashHandler() {
+bool startCrashHandler(std::string appPath) {
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
return false;
}
@@ -73,6 +90,10 @@ bool startCrashHandler() {
annotations["build_number"] = BuildInfo::BUILD_NUMBER.toStdString();
annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString();
+ auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
+ annotations["machine_fingerprint"] = machineFingerPrint.toStdString();
+
+
arguments.push_back("--no-rate-limit");
// Setup Crashpad DB directory
@@ -82,7 +103,10 @@ bool startCrashHandler() {
const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName;
// Locate Crashpad handler
- const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe";
+ const QFileInfo interfaceBinary { QString::fromStdString(appPath) };
+ const QDir interfaceDir = interfaceBinary.dir();
+ assert(interfaceDir.exists(CRASHPAD_HANDLER_NAME));
+ const std::string CRASHPAD_HANDLER_PATH = interfaceDir.filePath(CRASHPAD_HANDLER_NAME).toStdString();
// Setup different file paths
base::FilePath::StringType dbPath;
@@ -101,20 +125,24 @@ bool startCrashHandler() {
// Enable automated uploads.
database->GetSettings()->SetUploadsEnabled(true);
+#ifdef Q_OS_WIN
AddVectoredExceptionHandler(0, vectoredExceptionHandler);
+#endif
return client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
}
void setCrashAnnotation(std::string name, std::string value) {
- std::lock_guard guard(annotationMutex);
- if (!crashpadAnnotations) {
- crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak
- crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo();
- crashpad_info->set_simple_annotations(crashpadAnnotations);
+ if (client) {
+ std::lock_guard guard(annotationMutex);
+ if (!crashpadAnnotations) {
+ crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak
+ crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo();
+ crashpad_info->set_simple_annotations(crashpadAnnotations);
+ }
+ std::replace(value.begin(), value.end(), ',', ';');
+ crashpadAnnotations->SetKeyValue(name, value);
}
- std::replace(value.begin(), value.end(), ',', ';');
- crashpadAnnotations->SetKeyValue(name, value);
}
#endif
diff --git a/interface/src/CrashHandler_None.cpp b/interface/src/CrashHandler_None.cpp
index cba585f7b7..77b8ab332e 100644
--- a/interface/src/CrashHandler_None.cpp
+++ b/interface/src/CrashHandler_None.cpp
@@ -9,14 +9,15 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+#if !defined(HAS_CRASHPAD) && !defined(HAS_BREAKPAD)
+
#include "CrashHandler.h"
#include
+
#include
-#if !defined(HAS_CRASHPAD) && !defined(HAS_BREAKPAD)
-
-bool startCrashHandler() {
+bool startCrashHandler(std::string appPath) {
qDebug() << "No crash handler available.";
return false;
}
diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp
index d39aaa4f1a..e3a99475ef 100644
--- a/interface/src/DiscoverabilityManager.cpp
+++ b/interface/src/DiscoverabilityManager.cpp
@@ -97,7 +97,7 @@ void DiscoverabilityManager::updateLocation() {
locationObject.insert(AVAILABILITY_KEY_IN_LOCATION, findableByString(static_cast(_mode.get())));
JSONCallbackParameters callbackParameters;
- callbackParameters.jsonCallbackReceiver = this;
+ callbackParameters.callbackReceiver = this;
callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse";
// figure out if we'll send a fresh location or just a simple heartbeat
@@ -121,7 +121,7 @@ void DiscoverabilityManager::updateLocation() {
// we still send a heartbeat to the metaverse server for stats collection
JSONCallbackParameters callbackParameters;
- callbackParameters.jsonCallbackReceiver = this;
+ callbackParameters.callbackReceiver = this;
callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse";
accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional,
@@ -136,7 +136,7 @@ void DiscoverabilityManager::updateLocation() {
setCrashAnnotation("address", currentAddress.toString().toStdString());
}
-void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) {
+void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply* requestReply) {
auto dataObject = AccountManager::dataObjectFromResponse(requestReply);
if (!dataObject.isEmpty()) {
diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h
index 0c62ad5663..fc0b1fa759 100644
--- a/interface/src/DiscoverabilityManager.h
+++ b/interface/src/DiscoverabilityManager.h
@@ -51,7 +51,7 @@ public:
static QString findableByString(Discoverability::Mode discoverabilityMode);
private slots:
- void handleHeartbeatResponse(QNetworkReply& requestReply);
+ void handleHeartbeatResponse(QNetworkReply* requestReply);
private:
DiscoverabilityManager();
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index a102f2a42e..8524c40262 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -158,46 +158,6 @@ Menu::Menu() {
// Edit > Reload All Content
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
-
- MenuWrapper* avatarMenu = addMenu("Avatar");
- auto avatarManager = DependencyManager::get();
- auto avatar = avatarManager->getMyAvatar();
-
- // Avatar > Size
- MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size");
- // Avatar > Size > Increase
- addActionToQMenuAndActionHash(avatarSizeMenu,
- MenuOption::IncreaseAvatarSize,
- 0, // QML Qt::Key_Plus,
- avatar.get(), SLOT(increaseSize()));
-
- // Avatar > Size > Decrease
- addActionToQMenuAndActionHash(avatarSizeMenu,
- MenuOption::DecreaseAvatarSize,
- 0, // QML Qt::Key_Minus,
- avatar.get(), SLOT(decreaseSize()));
-
- // Avatar > Size > Reset
- addActionToQMenuAndActionHash(avatarSizeMenu,
- MenuOption::ResetAvatarSize,
- 0, // QML Qt::Key_Equal,
- avatar.get(), SLOT(resetSize()));
-
- // Avatar > Reset Sensors
- addActionToQMenuAndActionHash(avatarMenu,
- MenuOption::ResetSensors,
- 0, // QML Qt::Key_Apostrophe,
- qApp, SLOT(resetSensors()));
-
- // Avatar > Attachments...
- action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments);
- connect(action, &QAction::triggered, [] {
- qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"),
- QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog");
- });
-
- auto avatarBookmarks = DependencyManager::get();
- avatarBookmarks->setupMenus(this, avatarMenu);
// Display menu ----------------------------------
// FIXME - this is not yet matching Alan's spec because it doesn't have
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
@@ -256,8 +216,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView,
0, true, qApp, SLOT(rotationModeChanged()));
- addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
-
// View > Enter First Person Mode in HMD
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPersonHMD, 0, true);
@@ -283,7 +241,7 @@ Menu::Menu() {
MenuWrapper* settingsMenu = addMenu("Settings");
// Settings > General...
- action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr, QAction::PreferencesRole);
+ action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr);
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"),
QString("hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog");
@@ -317,13 +275,6 @@ Menu::Menu() {
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
});
- // Settings > Avatar...
- action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
- connect(action, &QAction::triggered, [] {
- qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
- QString("hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog");
- });
-
// Settings > Developer Menu
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));
@@ -582,6 +533,9 @@ Menu::Menu() {
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false);
connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); });
+ auto avatarManager = DependencyManager::get();
+ auto avatar = avatarManager->getMyAvatar();
+
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true);
connect(action, &QAction::triggered, [this, avatar]{
avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping));
@@ -818,6 +772,9 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false,
qApp, SLOT(updateVerboseLogging()));
+ // Developer > Show Overlays
+ addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true);
+
#if 0 /// -------------- REMOVED FOR NOW --------------
addDisabledActionAndSeparator(navigateMenu, "History");
QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack()));
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index ed2eb6a2db..40530b7701 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -14,6 +14,9 @@
#include
#include
+#include
+
+#include "AvatarLogging.h"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
@@ -36,13 +39,13 @@
#include
#include
#include
-#include
#include
#include "Application.h"
#include "InterfaceLogging.h"
#include "Menu.h"
#include "MyAvatar.h"
+#include "OtherAvatar.h"
#include "SceneScriptingInterface.h"
// 50 times per second - target is 45hz, but this helps account for any small deviations
@@ -54,6 +57,13 @@ static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND /
// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
const QUuid MY_AVATAR_KEY; // NULL key
+namespace {
+ // For an unknown avatar-data packet, wait this long before requesting the identity.
+ constexpr std::chrono::milliseconds REQUEST_UNKNOWN_IDENTITY_DELAY { 5 * 1000 };
+ constexpr int REQUEST_UNKNOWN_IDENTITY_TRANSMITS = 3;
+}
+using std::chrono::steady_clock;
+
AvatarManager::AvatarManager(QObject* parent) :
_avatarsToFade(),
_myAvatar(new MyAvatar(qApp->thread()), [](MyAvatar* ptr) { ptr->deleteLater(); })
@@ -118,6 +128,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
_lastSendAvatarDataTime = now;
_myAvatarSendRate.increment();
}
+
}
@@ -192,6 +203,15 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
while (!sortedAvatars.empty()) {
const SortableAvatar& sortData = sortedAvatars.top();
const auto avatar = std::static_pointer_cast(sortData.getAvatar());
+ const auto otherAvatar = std::static_pointer_cast(sortData.getAvatar());
+
+ // if the geometry is loaded then turn off the orb
+ if (avatar->getSkeletonModel()->isLoaded()) {
+ // remove the orb if it is there
+ otherAvatar->removeOrb();
+ } else {
+ otherAvatar->updateOrbPosition();
+ }
bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID());
if (ignoring) {
@@ -277,6 +297,28 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
simulateAvatarFades(deltaTime);
+ // Check on avatars with pending identities:
+ steady_clock::time_point now = steady_clock::now();
+ QWriteLocker writeLock(&_hashLock);
+ for (auto pendingAvatar = _pendingAvatars.begin(); pendingAvatar != _pendingAvatars.end(); ++pendingAvatar) {
+ if (now - pendingAvatar->creationTime >= REQUEST_UNKNOWN_IDENTITY_DELAY) {
+ // Too long without an ID
+ sendIdentityRequest(pendingAvatar->avatar->getID());
+ if (++pendingAvatar->transmits >= REQUEST_UNKNOWN_IDENTITY_TRANSMITS) {
+ qCDebug(avatars) << "Requesting identity for unknown avatar (final request)" <<
+ pendingAvatar->avatar->getID().toString();
+
+ pendingAvatar = _pendingAvatars.erase(pendingAvatar);
+ if (pendingAvatar == _pendingAvatars.end()) {
+ break;
+ }
+ } else {
+ pendingAvatar->creationTime = now;
+ qCDebug(avatars) << "Requesting identity for unknown avatar" << pendingAvatar->avatar->getID().toString();
+ }
+ }
+ }
+
_avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC;
}
@@ -289,6 +331,20 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen
}
}
+void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const {
+ auto nodeList = DependencyManager::get();
+ nodeList->eachMatchingNode(
+ [&](const SharedNodePointer& node)->bool {
+ return node->getType() == NodeType::AvatarMixer && node->getActiveSocket();
+ },
+ [&](const SharedNodePointer& node) {
+ auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true);
+ packet->write(avatarID.toRfc4122());
+ nodeList->sendPacket(std::move(packet), *node);
+ ++_identityRequestsSent;
+ });
+}
+
void AvatarManager::simulateAvatarFades(float deltaTime) {
if (_avatarsToFade.empty()) {
return;
@@ -684,3 +740,49 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
DependencyManager::get()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer);
}
}
+
+ QVariantMap AvatarManager::getPalData(const QList specificAvatarIdentifiers) {
+ QJsonArray palData;
+
+ auto avatarMap = getHashCopy();
+ AvatarHash::iterator itr = avatarMap.begin();
+ while (itr != avatarMap.end()) {
+ std::shared_ptr avatar = std::static_pointer_cast(*itr);
+ QString currentSessionUUID = avatar->getSessionUUID().toString();
+ if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) {
+ QJsonObject thisAvatarPalData;
+
+ auto myAvatar = DependencyManager::get()->getMyAvatar();
+
+ if (currentSessionUUID == myAvatar->getSessionUUID().toString()) {
+ currentSessionUUID = "";
+ }
+
+ thisAvatarPalData.insert("sessionUUID", currentSessionUUID);
+ thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName());
+ thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness());
+ thisAvatarPalData.insert("isReplicated", avatar->getIsReplicated());
+
+ glm::vec3 position = avatar->getWorldPosition();
+ QJsonObject jsonPosition;
+ jsonPosition.insert("x", position.x);
+ jsonPosition.insert("y", position.y);
+ jsonPosition.insert("z", position.z);
+ thisAvatarPalData.insert("position", jsonPosition);
+
+ float palOrbOffset = 0.2f;
+ int headIndex = avatar->getJointIndex("Head");
+ if (headIndex > 0) {
+ glm::vec3 jointTranslation = avatar->getAbsoluteJointTranslationInObjectFrame(headIndex);
+ palOrbOffset = jointTranslation.y / 2;
+ }
+ thisAvatarPalData.insert("palOrbOffset", palOrbOffset);
+
+ palData.append(thisAvatarPalData);
+ }
+ ++itr;
+ }
+ QJsonObject doc;
+ doc.insert("data", palData);
+ return doc.toVariantMap();
+}
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 9d9466264b..ecf9a2d735 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -82,6 +82,7 @@ public:
void updateMyAvatar(float deltaTime);
void updateOtherAvatars(float deltaTime);
+ void sendIdentityRequest(const QUuid& avatarID) const;
void postUpdate(float deltaTime, const render::ScenePointer& scene);
@@ -160,7 +161,19 @@ public:
*/
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
+ /**jsdoc
+ * Used in the PAL for getting PAL-related data about avatars nearby. Using this method is faster
+ * than iterating over each avatar and obtaining data about them in JavaScript, as that method
+ * locks and unlocks each avatar's data structure potentially hundreds of times per update tick.
+ * @function AvatarManager.getPalData
+ * @param {string[]} specificAvatarIdentifiers - A list of specific Avatar Identifiers about
+ * which you want to get PAL data
+ * @returns {object}
+ */
+ Q_INVOKABLE QVariantMap getPalData(const QList specificAvatarIdentifiers = QList());
+
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
+ int getIdentityRequestsSent() const { return _identityRequestsSent; }
public slots:
@@ -198,6 +211,7 @@ private:
int _numAvatarsNotUpdated { 0 };
float _avatarSimulationTime { 0.0f };
bool _shouldRender { true };
+ mutable int _identityRequestsSent { 0 };
};
#endif // hifi_AvatarManager_h
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 76714170fb..fd121055a1 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -88,6 +88,10 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
const float MyAvatar::ZOOM_MAX = 25.0f;
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
const float MIN_SCALE_CHANGED_DELTA = 0.001f;
+const int MODE_READINGS_RING_BUFFER_SIZE = 500;
+const float CENTIMETERS_PER_METER = 100.0f;
+
+//#define DEBUG_DRAW_HMD_MOVING_AVERAGE
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
@@ -108,6 +112,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_hmdSensorMatrix(),
_hmdSensorOrientation(),
_hmdSensorPosition(),
+ _recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE),
_bodySensorMatrix(),
_goToPending(false),
_goToPosition(),
@@ -257,6 +262,26 @@ void MyAvatar::setDominantHand(const QString& hand) {
}
}
+void MyAvatar::requestDisableHandTouch() {
+ std::lock_guard guard(_disableHandTouchMutex);
+ _disableHandTouchCount++;
+ emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0);
+}
+
+void MyAvatar::requestEnableHandTouch() {
+ std::lock_guard guard(_disableHandTouchMutex);
+ _disableHandTouchCount = std::max(_disableHandTouchCount - 1, 0);
+ emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0);
+}
+
+void MyAvatar::disableHandTouchForID(const QUuid& entityID) {
+ emit disableHandTouchForIDChanged(entityID, true);
+}
+
+void MyAvatar::enableHandTouchForID(const QUuid& entityID) {
+ emit disableHandTouchForIDChanged(entityID, false);
+}
+
void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) {
QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
engine->globalObject().setProperty("MyAvatar", value);
@@ -414,7 +439,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
void MyAvatar::update(float deltaTime) {
// update moving average of HMD facing in xz plane.
- const float HMD_FACING_TIMESCALE = 4.0f; // very slow average
+ const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
+
float tau = deltaTime / HMD_FACING_TIMESCALE;
_headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
@@ -423,6 +449,12 @@ void MyAvatar::update(float deltaTime) {
_smoothOrientationTimer += deltaTime;
}
+ float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y;
+ int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER);
+ _recentModeReadings.insert(newHeightReadingInCentimeters);
+ setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
+ setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
+
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
@@ -714,7 +746,8 @@ void MyAvatar::simulate(float deltaTime) {
entityTree->recurseTreeWithOperator(&moveOperator);
}
});
- _characterController.setFlyingAllowed(zoneAllowsFlying && _enableFlying);
+ bool isPhysicsEnabled = qApp->isPhysicsEnabled();
+ _characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
_characterController.setCollisionlessAllowed(collisionlessAllowed);
}
@@ -1097,8 +1130,8 @@ void MyAvatar::saveData() {
settings.setValue("displayName", _displayName);
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
- settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving);
settings.setValue("userHeight", getUserHeight());
+ settings.setValue("enabledFlying", getFlyingEnabled());
settings.endGroup();
}
@@ -1248,11 +1281,10 @@ void MyAvatar::loadData() {
settings.remove("avatarEntityData");
}
setAvatarEntityDataChanged(true);
-
+ setFlyingEnabled(settings.value("enabledFlying").toBool());
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
- setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool());
setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower());
setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble());
settings.endGroup();
@@ -1591,18 +1623,26 @@ void MyAvatar::removeAvatarEntities() {
QVariantList MyAvatar::getAvatarEntitiesVariant() {
QVariantList avatarEntitiesData;
QScriptEngine scriptEngine;
- forEachChild([&](SpatiallyNestablePointer child) {
- if (child->getNestableType() == NestableType::Entity) {
- auto modelEntity = std::dynamic_pointer_cast(child);
- if (modelEntity) {
- QVariantMap avatarEntityData;
- EntityItemProperties entityProperties = modelEntity->getProperties();
- QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
- avatarEntityData["properties"] = scriptProperties.toVariant();
- avatarEntitiesData.append(QVariant(avatarEntityData));
+ auto treeRenderer = DependencyManager::get();
+ EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
+ if (entityTree) {
+ AvatarEntityMap avatarEntities = getAvatarEntityData();
+ for (auto entityID : avatarEntities.keys()) {
+ auto entity = entityTree->findEntityByID(entityID);
+ if (!entity) {
+ continue;
}
+ QVariantMap avatarEntityData;
+ EncodeBitstreamParams params;
+ auto desiredProperties = entity->getEntityProperties(params);
+ desiredProperties += PROP_LOCAL_POSITION;
+ desiredProperties += PROP_LOCAL_ROTATION;
+ EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
+ QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
+ avatarEntityData["properties"] = scriptProperties.toVariant();
+ avatarEntitiesData.append(QVariant(avatarEntityData));
}
- });
+ }
return avatarEntitiesData;
}
@@ -1979,7 +2019,6 @@ QUrl MyAvatar::getAnimGraphUrl() const {
}
void MyAvatar::setAnimGraphUrl(const QUrl& url) {
-
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAnimGraphUrl", Q_ARG(QUrl, url));
return;
@@ -1988,6 +2027,9 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) {
if (_currentAnimGraphUrl.get() == url) {
return;
}
+
+ emit animGraphUrlChanged(url);
+
destroyAnimGraph();
_skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render.
@@ -2172,6 +2214,15 @@ void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement)
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
}
+void MyAvatar::setRotationRecenterFilterLength(float length) {
+ const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f;
+ _rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length);
+}
+
+void MyAvatar::setRotationThreshold(float angleRadians) {
+ _rotationThreshold = angleRadians;
+}
+
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
@@ -2812,6 +2863,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) {
}
_characterController.setCollisionless(!enabled);
+ emit collisionsEnabledChanged(enabled);
}
bool MyAvatar::getCollisionsEnabled() {
@@ -3135,6 +3187,148 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const {
return worldToSensorMat * avatarToWorldMat * avatarHipsMat;
}
+static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) {
+ return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0);
+}
+
+static bool withinBaseOfSupport(const controller::Pose& head) {
+ float userScale = 1.0f;
+
+ glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD);
+ glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD);
+ glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD);
+ glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD);
+
+ bool isWithinSupport = false;
+ if (head.isValid()) {
+ bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation());
+ bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation());
+ bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) &&
+ isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation()));
+ isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase);
+ }
+ return isWithinSupport;
+}
+
+static bool headAngularVelocityBelowThreshold(const controller::Pose& head) {
+ glm::vec3 xzPlaneAngularVelocity(0.0f, 0.0f, 0.0f);
+ if (head.isValid()) {
+ xzPlaneAngularVelocity.x = head.getAngularVelocity().x;
+ xzPlaneAngularVelocity.z = head.getAngularVelocity().z;
+ }
+ float magnitudeAngularVelocity = glm::length(xzPlaneAngularVelocity);
+ bool isBelowThreshold = (magnitudeAngularVelocity < DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD);
+
+ return isBelowThreshold;
+}
+
+static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) {
+ bool isWithinThreshold = true;
+ if (head.isValid()) {
+ isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD;
+ }
+ return isWithinThreshold;
+}
+
+float MyAvatar::computeStandingHeightMode(const controller::Pose& head) {
+ const float MODE_CORRECTION_FACTOR = 0.02f;
+ int greatestFrequency = 0;
+ int mode = 0;
+ // init mode in meters to the current mode
+ float modeInMeters = getCurrentStandingHeight();
+ if (head.isValid()) {
+ std::map freq;
+ for(auto recentModeReadingsIterator = _recentModeReadings.begin(); recentModeReadingsIterator != _recentModeReadings.end(); ++recentModeReadingsIterator) {
+ freq[*recentModeReadingsIterator] += 1;
+ if (freq[*recentModeReadingsIterator] > greatestFrequency) {
+ greatestFrequency = freq[*recentModeReadingsIterator];
+ mode = *recentModeReadingsIterator;
+ }
+ }
+
+ modeInMeters = ((float)mode) / CENTIMETERS_PER_METER;
+ if (!(modeInMeters > getCurrentStandingHeight())) {
+ // if not greater check for a reset
+ if (getResetMode() && qApp->isHMDMode()) {
+ setResetMode(false);
+ float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER);
+ modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER);
+ _recentModeReadings.clear();
+
+ } else {
+ // if not greater and no reset, keep the mode as it is
+ modeInMeters = getCurrentStandingHeight();
+
+ }
+ }
+ }
+ return modeInMeters;
+}
+
+static bool handDirectionMatchesHeadDirection(const controller::Pose& leftHand, const controller::Pose& rightHand, const controller::Pose& head) {
+ const float VELOCITY_EPSILON = 0.02f;
+ bool leftHandDirectionMatchesHead = true;
+ bool rightHandDirectionMatchesHead = true;
+ glm::vec3 xzHeadVelocity(head.velocity.x, 0.0f, head.velocity.z);
+ if (leftHand.isValid() && head.isValid()) {
+ glm::vec3 xzLeftHandVelocity(leftHand.velocity.x, 0.0f, leftHand.velocity.z);
+ if ((glm::length(xzLeftHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) {
+ float handDotHeadLeft = glm::dot(glm::normalize(xzLeftHandVelocity), glm::normalize(xzHeadVelocity));
+ leftHandDirectionMatchesHead = ((handDotHeadLeft > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD));
+ } else {
+ leftHandDirectionMatchesHead = false;
+ }
+ }
+ if (rightHand.isValid() && head.isValid()) {
+ glm::vec3 xzRightHandVelocity(rightHand.velocity.x, 0.0f, rightHand.velocity.z);
+ if ((glm::length(xzRightHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) {
+ float handDotHeadRight = glm::dot(glm::normalize(xzRightHandVelocity), glm::normalize(xzHeadVelocity));
+ rightHandDirectionMatchesHead = (handDotHeadRight > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD);
+ } else {
+ rightHandDirectionMatchesHead = false;
+ }
+ }
+ return leftHandDirectionMatchesHead && rightHandDirectionMatchesHead;
+}
+
+static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, const controller::Pose& rightHand) {
+ float leftHandXZAngularVelocity = 0.0f;
+ float rightHandXZAngularVelocity = 0.0f;
+ if (leftHand.isValid()) {
+ glm::vec3 xzLeftHandAngularVelocity(leftHand.angularVelocity.x, 0.0f, leftHand.angularVelocity.z);
+ leftHandXZAngularVelocity = glm::length(xzLeftHandAngularVelocity);
+ }
+ if (rightHand.isValid()) {
+ glm::vec3 xzRightHandAngularVelocity(rightHand.angularVelocity.x, 0.0f, rightHand.angularVelocity.z);
+ rightHandXZAngularVelocity = glm::length(xzRightHandAngularVelocity);
+ }
+ return ((leftHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD) &&
+ (rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD));
+}
+
+static bool headVelocityGreaterThanThreshold(const controller::Pose& head) {
+ float headVelocityMagnitude = 0.0f;
+ if (head.isValid()) {
+ headVelocityMagnitude = glm::length(head.getVelocity());
+ }
+ return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD;
+}
+
+glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) {
+ const float AVERAGING_RATE = 0.03f;
+ return safeLerp(_averageHeadRotation, head.getRotation(), AVERAGING_RATE);
+}
+
+static bool isHeadLevel(const controller::Pose& head, const glm::quat& averageHeadRotation) {
+ glm::vec3 diffFromAverageEulers(0.0f, 0.0f, 0.0f);
+ if (head.isValid()) {
+ glm::vec3 averageHeadEulers = glm::degrees(safeEulerAngles(averageHeadRotation));
+ glm::vec3 currentHeadEulers = glm::degrees(safeEulerAngles(head.getRotation()));
+ diffFromAverageEulers = averageHeadEulers - currentHeadEulers;
+ }
+ return ((fabs(diffFromAverageEulers.x) < DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE) && (fabs(diffFromAverageEulers.z) < DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE));
+}
+
float MyAvatar::getUserHeight() const {
return _userHeight.get();
}
@@ -3301,7 +3495,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
}
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
- const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
+ const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold());
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
}
@@ -3328,7 +3522,37 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
}
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
+}
+bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const {
+
+ // get the current readings
+ controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
+ controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
+ controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND);
+
+ bool stepDetected = false;
+ if (!withinBaseOfSupport(currentHeadPose) &&
+ headAngularVelocityBelowThreshold(currentHeadPose) &&
+ isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) &&
+ handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) &&
+ handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) &&
+ headVelocityGreaterThanThreshold(currentHeadPose) &&
+ isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) {
+ // a step is detected
+ stepDetected = true;
+ } else {
+ glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips"));
+ glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head"));
+ glm::vec3 currentHeadPosition = currentHeadPose.getTranslation();
+ float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition);
+ if (!isActive(Horizontal) &&
+ (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) {
+ myAvatar.setResetMode(true);
+ stepDetected = true;
+ }
+ }
+ return stepDetected;
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@@ -3347,16 +3571,25 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Rotation);
+ myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing);
}
- if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
- activate(Horizontal);
+ if (myAvatar.getCenterOfGravityModelEnabled()) {
+ if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) {
+ activate(Horizontal);
+ }
+ } else {
+ if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
+ activate(Horizontal);
+ }
}
+
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Vertical);
}
} else {
if (!isActive(Rotation) && getForceActivateRotation()) {
activate(Rotation);
+ myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing);
setForceActivateRotation(false);
}
if (!isActive(Horizontal) && getForceActivateHorizontal()) {
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 7cb1cfb4b4..9b5ddd360d 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -31,6 +31,7 @@
#include "AtRestDetector.h"
#include "MyCharacterController.h"
+#include "RingBufferHistory.h"
#include
class AvatarActionHold;
@@ -195,6 +196,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement)
Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement)
Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement)
+ Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength)
+ Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
@@ -250,7 +253,7 @@ public:
Q_ENUM(DriveKeys)
explicit MyAvatar(QThread* thread);
- ~MyAvatar();
+ virtual ~MyAvatar();
void instantiableAvatar() override {};
void registerMetaTypes(ScriptEnginePointer engine);
@@ -469,16 +472,6 @@ public:
* @param {boolean} on
*/
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
- /**jsdoc
- * @function MyAvatar.getClearOverlayWhenMoving
- * @returns {boolean}
- */
- Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; }
- /**jsdoc
- * @function MyAvatar.setClearOverlayWhenMoving
- * @param {boolean} on
- */
- Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; }
/**jsdoc
@@ -512,6 +505,28 @@ public:
* @returns {boolean}
*/
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
+ /**jsdoc
+ * Request to enable hand touch effect globally
+ * @function MyAvatar.requestEnableHandTouch
+ */
+ Q_INVOKABLE void requestEnableHandTouch();
+ /**jsdoc
+ * Request to disable hand touch effect globally
+ * @function MyAvatar.requestDisableHandTouch
+ */
+ Q_INVOKABLE void requestDisableHandTouch();
+ /**jsdoc
+ * Disables hand touch effect on a specific entity
+ * @function MyAvatar.disableHandTouchForID
+ * @param {Uuid} entityID - ID of the entity that will disable hand touch effect
+ */
+ Q_INVOKABLE void disableHandTouchForID(const QUuid& entityID);
+ /**jsdoc
+ * Enables hand touch effect on a specific entity
+ * @function MyAvatar.enableHandTouchForID
+ * @param {Uuid} entityID - ID of the entity that will enable hand touch effect
+ */
+ Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID);
bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); }
void setUseAdvancedMovementControls(bool useAdvancedMovementControls)
@@ -890,6 +905,13 @@ public:
virtual void rebuildCollisionShape() override;
const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
+ void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; }
+ float getCurrentStandingHeight() const { return _currentStandingHeight; }
+ void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; }
+ const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; }
+ void setAverageHeadRotation(glm::quat rotation) { _averageHeadRotation = rotation; }
+ bool getResetMode() const { return _resetMode; }
+ void setResetMode(bool hasBeenReset) { _resetMode = hasBeenReset; }
void setControllerPoseInSensorFrame(controller::Action action, const controller::Pose& pose);
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
@@ -898,7 +920,12 @@ public:
bool hasDriveInput() const;
- QVariantList getAvatarEntitiesVariant();
+ /**jsdoc
+ * Function returns list of avatar entities
+ * @function MyAvatar.getAvatarEntitiesVariant()
+ * @returns {object[]}
+ */
+ Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
void removeAvatarEntities();
/**jsdoc
@@ -1025,6 +1052,9 @@ public:
bool isReadyForPhysics() const;
+ float computeStandingHeightMode(const controller::Pose& head);
+ glm::quat computeAverageHeadRotation(const controller::Pose& head);
+
public slots:
/**jsdoc
@@ -1304,6 +1334,22 @@ signals:
*/
void collisionWithEntity(const Collision& collision);
+ /**jsdoc
+ * Triggered when collisions with avatar enabled or disabled
+ * @function MyAvatar.collisionsEnabledChanged
+ * @param {boolean} enabled
+ * @returns {Signal}
+ */
+ void collisionsEnabledChanged(bool enabled);
+
+ /**jsdoc
+ * Triggered when avatar's animation url changes
+ * @function MyAvatar.animGraphUrlChanged
+ * @param {url} url
+ * @returns {Signal}
+ */
+ void animGraphUrlChanged(const QUrl& url);
+
/**jsdoc
* @function MyAvatar.energyChanged
* @param {number} energy
@@ -1368,6 +1414,23 @@ signals:
*/
void scaleChanged();
+ /**jsdoc
+ * Triggered when hand touch is globally enabled or disabled
+ * @function MyAvatar.shouldDisableHandTouchChanged
+ * @param {boolean} shouldDisable
+ * @returns {Signal}
+ */
+ void shouldDisableHandTouchChanged(bool shouldDisable);
+
+ /**jsdoc
+ * Triggered when hand touch is enabled or disabled for an specific entity
+ * @function MyAvatar.disableHandTouchForIDChanged
+ * @param {Uuid} entityID - ID of the entity that will enable hand touch effect
+ * @param {boolean} disable
+ * @returns {Signal}
+ */
+ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
+
private slots:
void leaveDomain();
@@ -1397,6 +1460,10 @@ private:
bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); }
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
+ void setRotationRecenterFilterLength(float length);
+ float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; }
+ void setRotationThreshold(float angleRadians);
+ float getRotationThreshold() const { return _rotationThreshold; }
bool isMyAvatar() const override { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
virtual glm::vec3 getSkeletonPosition() const override;
@@ -1434,7 +1501,7 @@ private:
std::array _driveKeys;
std::bitset _disabledDriveKeys;
- bool _enableFlying { true };
+ bool _enableFlying { false };
bool _wasPushing { false };
bool _isPushing { false };
bool _isBeingPushed { false };
@@ -1496,7 +1563,6 @@ private:
ThreadSafeValueCache _prefOverrideAnimGraphUrl;
QUrl _fstAnimGraphOverrideUrl;
bool _useSnapTurn { true };
- bool _clearOverlayWhenMoving { true };
QString _dominantHand { DOMINANT_RIGHT_HAND };
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees
@@ -1506,6 +1572,8 @@ private:
float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT };
float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT };
std::atomic _hasScriptedBlendShapes { false };
+ std::atomic _rotationRecenterFilterLength { 4.0f };
+ std::atomic _rotationThreshold { 0.5235f }; // 30 degrees in radians
// working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
glm::mat4 _sensorToWorldMatrix { glm::mat4() };
@@ -1517,6 +1585,11 @@ private:
// cache head controller pose in sensor space
glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space)
glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space)
+ glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f };
+
+ float _currentStandingHeight { 0.0f };
+ bool _resetMode { true };
+ RingBufferHistory _recentModeReadings;
// cache of the current body position and orientation of the avatar's body,
// in sensor space.
@@ -1544,6 +1617,7 @@ private:
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
+ bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const;
@@ -1593,6 +1667,7 @@ private:
// all poses are in sensor-frame
std::map _controllerPoseMap;
mutable std::mutex _controllerPoseMapMutex;
+ mutable std::mutex _disableHandTouchMutex;
bool _centerOfGravityModelEnabled { true };
bool _hmdLeanRecenterEnabled { true };
@@ -1632,7 +1707,8 @@ private:
// load avatar scripts once when rig is ready
bool _shouldLoadScripts { false };
- bool _haveReceivedHeightLimitsFromDomain = { false };
+ bool _haveReceivedHeightLimitsFromDomain { false };
+ int _disableHandTouchCount { 0 };
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index c15b00ca19..0fc5e7521e 100644
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
}
glm::mat4 hipsMat;
- if (myAvatar->getCenterOfGravityModelEnabled()) {
+ if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying) {
// then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel();
} else {
@@ -109,6 +109,11 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f));
+ glm::mat4 rigToAvatarMatrix = Matrices::Y_180;
+ glm::mat4 avatarToWorldMatrix = createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition());
+ glm::mat4 sensorToWorldMatrix = myAvatar->getSensorToWorldMatrix();
+ params.rigToSensorMatrix = glm::inverse(sensorToWorldMatrix) * avatarToWorldMatrix * rigToAvatarMatrix;
+
// input action is the highest priority source for head orientation.
auto avatarHeadPose = myAvatar->getControllerPoseInAvatarFrame(controller::Action::HEAD);
if (avatarHeadPose.isValid()) {
diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp
new file mode 100644
index 0000000000..5e51658128
--- /dev/null
+++ b/interface/src/avatar/OtherAvatar.cpp
@@ -0,0 +1,60 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// 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 "OtherAvatar.h"
+#include "Application.h"
+
+OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) {
+ // give the pointer to our head to inherited _headData variable from AvatarData
+ _headData = new Head(this);
+ _skeletonModel = std::make_shared(this, nullptr);
+ _skeletonModel->setLoadingPriority(OTHERAVATAR_LOADING_PRIORITY);
+ connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
+ connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady);
+ connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset);
+
+ // add the purple orb
+ createOrb();
+}
+
+OtherAvatar::~OtherAvatar() {
+ removeOrb();
+}
+
+void OtherAvatar::removeOrb() {
+ if (qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) {
+ qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID);
+ }
+}
+
+void OtherAvatar::updateOrbPosition() {
+ if (_otherAvatarOrbMeshPlaceholder != nullptr) {
+ _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition());
+ }
+}
+
+void OtherAvatar::createOrb() {
+ if (_otherAvatarOrbMeshPlaceholderID == UNKNOWN_OVERLAY_ID ||
+ !qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) {
+ _otherAvatarOrbMeshPlaceholder = std::make_shared();
+ _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f);
+ _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF });
+ _otherAvatarOrbMeshPlaceholder->setIsSolid(false);
+ _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5);
+ _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0);
+ _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0);
+ _otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true);
+ _otherAvatarOrbMeshPlaceholder->setDrawInFront(false);
+ _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder);
+ // Position focus
+ _otherAvatarOrbMeshPlaceholder->setWorldOrientation(glm::quat(0.0f, 0.0f, 0.0f, 1.0));
+ _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition());
+ _otherAvatarOrbMeshPlaceholder->setDimensions(glm::vec3(0.5f, 0.5f, 0.5f));
+ _otherAvatarOrbMeshPlaceholder->setVisible(true);
+ }
+}
diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h
new file mode 100644
index 0000000000..f33952b78b
--- /dev/null
+++ b/interface/src/avatar/OtherAvatar.h
@@ -0,0 +1,32 @@
+//
+// Created by Bradley Austin Davis on 2017/04/27
+// 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_OtherAvatar_h
+#define hifi_OtherAvatar_h
+
+#include
+#include "ui/overlays/Overlays.h"
+#include "ui/overlays/Sphere3DOverlay.h"
+#include "InterfaceLogging.h"
+
+class OtherAvatar : public Avatar {
+public:
+ explicit OtherAvatar(QThread* thread);
+ virtual ~OtherAvatar();
+
+ virtual void instantiableAvatar() override { };
+ virtual void createOrb() override;
+ void updateOrbPosition();
+ void removeOrb();
+
+protected:
+ std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr };
+ OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
+};
+
+#endif // hifi_OtherAvatar_h
diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp
index 69698e82a6..702251f867 100644
--- a/interface/src/commerce/Ledger.cpp
+++ b/interface/src/commerce/Ledger.cpp
@@ -28,15 +28,15 @@
// account synthesizes a result {status: 'success', data: {keyStatus: "preexisting"|"conflicting"|"ok"}}
-QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) {
- QByteArray response = reply.readAll();
+QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) {
+ QByteArray response = reply->readAll();
QJsonObject data = QJsonDocument::fromJson(response).object();
qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
return data;
}
// Non-200 responses are not json:
-QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) {
- QString response = reply.readAll();
+QJsonObject Ledger::failResponse(const QString& label, QNetworkReply* reply) {
+ QString response = reply->readAll();
qWarning(commerce) << "FAILED" << label << response;
// tempResult will be NULL if the response isn't valid JSON.
@@ -52,8 +52,8 @@ QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) {
return tempResult.object();
}
}
-#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply& reply) { emit NAME##Result(apiResponse(#NAME, reply)); }
-#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply& reply) { emit NAME##Result(failResponse(#NAME, reply)); }
+#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply* reply) { emit NAME##Result(apiResponse(#NAME, reply)); }
+#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply* reply) { emit NAME##Result(failResponse(#NAME, reply)); }
#define Handler(NAME) ApiHandler(NAME) FailHandler(NAME)
Handler(buy)
Handler(receiveAt)
@@ -68,7 +68,7 @@ Handler(updateItem)
void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) {
auto accountManager = DependencyManager::get();
const QString URL = "/api/v1/commerce/";
- JSONCallbackParameters callbackParams(this, success, this, fail);
+ JSONCallbackParameters callbackParams(this, success, fail);
qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact);
accountManager->sendRequest(URL + endpoint,
authType,
@@ -223,12 +223,12 @@ QString transactionString(const QJsonObject& valueObject) {
}
static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
-void Ledger::historySuccess(QNetworkReply& reply) {
+void Ledger::historySuccess(QNetworkReply* reply) {
// here we send a historyResult with some extra stuff in it
// Namely, the styled text we'd like to show. The issue is the
// QML cannot do that easily since it doesn't know what the wallet
// public key(s) are. Let's keep it that way
- QByteArray response = reply.readAll();
+ QByteArray response = reply->readAll();
QJsonObject data = QJsonDocument::fromJson(response).object();
qInfo(commerce) << "history" << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
@@ -262,7 +262,7 @@ void Ledger::historySuccess(QNetworkReply& reply) {
emit historyResult(newData);
}
-void Ledger::historyFailure(QNetworkReply& reply) {
+void Ledger::historyFailure(QNetworkReply* reply) {
failResponse("history", reply);
}
@@ -273,10 +273,10 @@ void Ledger::history(const QStringList& keys, const int& pageNumber, const int&
keysQuery("history", "historySuccess", "historyFailure", params);
}
-void Ledger::accountSuccess(QNetworkReply& reply) {
+void Ledger::accountSuccess(QNetworkReply* reply) {
// lets set the appropriate stuff in the wallet now
auto wallet = DependencyManager::get();
- QByteArray response = reply.readAll();
+ QByteArray response = reply->readAll();
QJsonObject data = QJsonDocument::fromJson(response).object()["data"].toObject();
auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8());
@@ -312,7 +312,7 @@ void Ledger::accountSuccess(QNetworkReply& reply) {
emit accountResult(responseData);
}
-void Ledger::accountFailure(QNetworkReply& reply) {
+void Ledger::accountFailure(QNetworkReply* reply) {
failResponse("account", reply);
}
void Ledger::account() {
@@ -320,8 +320,8 @@ void Ledger::account() {
}
// The api/failResponse is called just for the side effect of logging.
-void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); }
-void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); }
+void Ledger::updateLocationSuccess(QNetworkReply* reply) { apiResponse("updateLocation", reply); }
+void Ledger::updateLocationFailure(QNetworkReply* reply) { failResponse("updateLocation", reply); }
void Ledger::updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings, const bool controlledFailure) {
auto wallet = DependencyManager::get();
auto walletScriptingInterface = DependencyManager::get();
@@ -349,11 +349,11 @@ void Ledger::updateLocation(const QString& asset_id, const QString& location, co
}
}
-void Ledger::certificateInfoSuccess(QNetworkReply& reply) {
+void Ledger::certificateInfoSuccess(QNetworkReply* reply) {
auto wallet = DependencyManager::get();
auto accountManager = DependencyManager::get();
- QByteArray response = reply.readAll();
+ QByteArray response = reply->readAll();
QJsonObject replyObject = QJsonDocument::fromJson(response).object();
QStringList keys = wallet->listPublicKeys();
@@ -366,7 +366,7 @@ void Ledger::certificateInfoSuccess(QNetworkReply& reply) {
qInfo(commerce) << "certificateInfo" << "response" << QJsonDocument(replyObject).toJson(QJsonDocument::Compact);
emit certificateInfoResult(replyObject);
}
-void Ledger::certificateInfoFailure(QNetworkReply& reply) {
+void Ledger::certificateInfoFailure(QNetworkReply* reply) {
emit certificateInfoResult(failResponse("certificateInfo", reply));
}
void Ledger::certificateInfo(const QString& certificateId) {
diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h
index 8a8fd2630a..ba2f167f4b 100644
--- a/interface/src/commerce/Ledger.h
+++ b/interface/src/commerce/Ledger.h
@@ -65,36 +65,36 @@ signals:
void updateCertificateStatus(const QString& certID, uint certStatus);
public slots:
- void buySuccess(QNetworkReply& reply);
- void buyFailure(QNetworkReply& reply);
- void receiveAtSuccess(QNetworkReply& reply);
- void receiveAtFailure(QNetworkReply& reply);
- void balanceSuccess(QNetworkReply& reply);
- void balanceFailure(QNetworkReply& reply);
- void inventorySuccess(QNetworkReply& reply);
- void inventoryFailure(QNetworkReply& reply);
- void historySuccess(QNetworkReply& reply);
- void historyFailure(QNetworkReply& reply);
- void accountSuccess(QNetworkReply& reply);
- void accountFailure(QNetworkReply& reply);
- void updateLocationSuccess(QNetworkReply& reply);
- void updateLocationFailure(QNetworkReply& reply);
- void certificateInfoSuccess(QNetworkReply& reply);
- void certificateInfoFailure(QNetworkReply& reply);
- void transferAssetToNodeSuccess(QNetworkReply& reply);
- void transferAssetToNodeFailure(QNetworkReply& reply);
- void transferAssetToUsernameSuccess(QNetworkReply& reply);
- void transferAssetToUsernameFailure(QNetworkReply& reply);
- void alreadyOwnedSuccess(QNetworkReply& reply);
- void alreadyOwnedFailure(QNetworkReply& reply);
- void availableUpdatesSuccess(QNetworkReply& reply);
- void availableUpdatesFailure(QNetworkReply& reply);
- void updateItemSuccess(QNetworkReply& reply);
- void updateItemFailure(QNetworkReply& reply);
+ void buySuccess(QNetworkReply* reply);
+ void buyFailure(QNetworkReply* reply);
+ void receiveAtSuccess(QNetworkReply* reply);
+ void receiveAtFailure(QNetworkReply* reply);
+ void balanceSuccess(QNetworkReply* reply);
+ void balanceFailure(QNetworkReply* reply);
+ void inventorySuccess(QNetworkReply* reply);
+ void inventoryFailure(QNetworkReply* reply);
+ void historySuccess(QNetworkReply* reply);
+ void historyFailure(QNetworkReply* reply);
+ void accountSuccess(QNetworkReply* reply);
+ void accountFailure(QNetworkReply* reply);
+ void updateLocationSuccess(QNetworkReply* reply);
+ void updateLocationFailure(QNetworkReply* reply);
+ void certificateInfoSuccess(QNetworkReply* reply);
+ void certificateInfoFailure(QNetworkReply* reply);
+ void transferAssetToNodeSuccess(QNetworkReply* reply);
+ void transferAssetToNodeFailure(QNetworkReply* reply);
+ void transferAssetToUsernameSuccess(QNetworkReply* reply);
+ void transferAssetToUsernameFailure(QNetworkReply* reply);
+ void alreadyOwnedSuccess(QNetworkReply* reply);
+ void alreadyOwnedFailure(QNetworkReply* reply);
+ void availableUpdatesSuccess(QNetworkReply* reply);
+ void availableUpdatesFailure(QNetworkReply* reply);
+ void updateItemSuccess(QNetworkReply* reply);
+ void updateItemFailure(QNetworkReply* reply);
private:
- QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
- QJsonObject failResponse(const QString& label, QNetworkReply& reply);
+ QJsonObject apiResponse(const QString& label, QNetworkReply* reply);
+ QJsonObject failResponse(const QString& label, QNetworkReply* reply);
void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request);
void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams);
void keysQuery(const QString& endpoint, const QString& success, const QString& fail);
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index b960c0b703..1f44343bdc 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -208,7 +208,7 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) {
ledger->alreadyOwned(marketplaceId);
}
-QString QmlCommerce::getInstalledApps() {
+QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
QString installedAppsFromMarketplace;
QStringList runningScripts = DependencyManager::get()->getRunningScripts();
@@ -217,6 +217,18 @@ QString QmlCommerce::getInstalledApps() {
foreach(QString appFileName, apps) {
installedAppsFromMarketplace += appFileName;
installedAppsFromMarketplace += ",";
+
+ // If we were supplied a "justInstalledAppID" argument, that means we're entering this function
+ // to get the new list of installed apps immediately after installing an app.
+ // In that case, the app we installed may not yet have its associated script running -
+ // that task is asynchronous and takes a nonzero amount of time. This is especially true
+ // for apps that are not in Interface's script cache.
+ // Thus, we protect against deleting the .app.json from the user's disk (below)
+ // by skipping that check for the app we just installed.
+ if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) {
+ continue;
+ }
+
QFile appFile(_appsPath + appFileName);
if (appFile.open(QIODevice::ReadOnly)) {
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
@@ -291,7 +303,8 @@ bool QmlCommerce::installApp(const QString& itemHref) {
return false;
}
- emit appInstalled(itemHref);
+ QFileInfo appFileInfo(appFile);
+ emit appInstalled(appFileInfo.baseName());
return true;
});
request->send();
@@ -321,7 +334,8 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) {
qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName();
}
- emit appUninstalled(itemHref);
+ QFileInfo appFileInfo(appFile);
+ emit appUninstalled(appFileInfo.baseName());
return true;
}
diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h
index a0c6916799..79d8e82e71 100644
--- a/interface/src/commerce/QmlCommerce.h
+++ b/interface/src/commerce/QmlCommerce.h
@@ -53,8 +53,8 @@ signals:
void contentSetChanged(const QString& contentSetHref);
- void appInstalled(const QString& appHref);
- void appUninstalled(const QString& appHref);
+ void appInstalled(const QString& appID);
+ void appUninstalled(const QString& appID);
protected:
Q_INVOKABLE void getWalletStatus();
@@ -86,7 +86,7 @@ protected:
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
- Q_INVOKABLE QString getInstalledApps();
+ Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = "");
Q_INVOKABLE bool installApp(const QString& appHref);
Q_INVOKABLE bool uninstallApp(const QString& appHref);
Q_INVOKABLE bool openApp(const QString& appHref);
diff --git a/interface/src/main.cpp b/interface/src/main.cpp
index d6665f1036..85a83d88d1 100644
--- a/interface/src/main.cpp
+++ b/interface/src/main.cpp
@@ -91,7 +91,7 @@ int main(int argc, const char* argv[]) {
qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled();
if (ual.isEnabled()) {
- auto crashHandlerStarted = startCrashHandler();
+ auto crashHandlerStarted = startCrashHandler(argv[0]);
qDebug() << "Crash handler started:" << crashHandlerStarted;
}
diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp
index 34f196ac70..bda06cda48 100644
--- a/interface/src/scripting/DesktopScriptingInterface.cpp
+++ b/interface/src/scripting/DesktopScriptingInterface.cpp
@@ -14,6 +14,8 @@
#include
#include
+#include
+
#include "Application.h"
#include "MainWindow.h"
#include
@@ -29,6 +31,14 @@ int DesktopScriptingInterface::getHeight() {
return size.height();
}
+QVariantMap DesktopScriptingInterface::getPresentationMode() {
+ static QVariantMap presentationModes {
+ { "VIRTUAL", Virtual },
+ { "NATIVE", Native }
+ };
+ return presentationModes;
+}
+
void DesktopScriptingInterface::setHUDAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}
@@ -41,3 +51,14 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title)
DependencyManager::get()->show(path, title);
}
+InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) {
+ if (QThread::currentThread() != thread()) {
+ InteractiveWindowPointer interactiveWindow = nullptr;
+ BLOCKING_INVOKE_METHOD(this, "createWindow",
+ Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow),
+ Q_ARG(QString, sourceUrl),
+ Q_ARG(QVariantMap, properties));
+ return interactiveWindow;
+ }
+ return new InteractiveWindow(sourceUrl, properties);;
+}
diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h
index e62a3584d6..db42b5ca54 100644
--- a/interface/src/scripting/DesktopScriptingInterface.h
+++ b/interface/src/scripting/DesktopScriptingInterface.h
@@ -13,20 +13,48 @@
#define hifi_DesktopScriptingInterface_h
#include
+#include
#include
+#include "InteractiveWindow.h"
+
+/**jsdoc
+ * @namespace Desktop
+ *
+ * @hifi-interface
+ * @hifi-client-entity
+ *
+ * @property {number} width
+ * @property {number} height
+ * @property {number} ALWAYS_ON_TOP - InteractiveWindow flag for always showing a window on top
+ * @property {number} CLOSE_BUTTON_HIDES - InteractiveWindow flag for hiding the window instead of closing on window close by user
+ */
class DesktopScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(int width READ getWidth) // Physical width of screen(s) including task bars and system menus
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
+ Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL)
+ Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL)
+ Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL)
+
public:
Q_INVOKABLE void setHUDAlpha(float alpha);
Q_INVOKABLE void show(const QString& path, const QString& title);
+ Q_INVOKABLE InteractiveWindowPointer createWindow(const QString& sourceUrl, const QVariantMap& properties = QVariantMap());
+
int getWidth();
int getHeight();
+
+
+private:
+ static int flagAlwaysOnTop() { return AlwaysOnTop; }
+ static int flagCloseButtonHides() { return CloseButtonHides; }
+
+ Q_INVOKABLE static QVariantMap getPresentationMode();
};
+
#endif // hifi_DesktopScriptingInterface_h
diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp
index 2f14c33dc7..afafe1a350 100644
--- a/interface/src/scripting/SettingsScriptingInterface.cpp
+++ b/interface/src/scripting/SettingsScriptingInterface.cpp
@@ -35,5 +35,8 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar
}
void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) {
- Setting::Handle(setting).set(value);
+ // Make a deep-copy of the string.
+ // Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine.
+ QString deepCopy = QString::fromUtf16(setting.utf16());
+ Setting::Handle(deepCopy).set(value);
}
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index 0aea7a02c5..ba86925581 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -86,10 +86,6 @@ void WindowScriptingInterface::raise() {
});
}
-void WindowScriptingInterface::raiseMainWindow() {
- raise();
-}
-
/// Display an alert box
/// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue
@@ -137,8 +133,13 @@ void WindowScriptingInterface::openUrl(const QUrl& url) {
if (url.scheme() == URL_SCHEME_HIFI) {
DependencyManager::get()->handleLookupString(url.toString());
} else {
+#if defined(Q_OS_ANDROID)
+ QList args = { url.toString() };
+ AndroidHelper::instance().requestActivity("WebView", true, args);
+#else
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
+#endif
}
}
}
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index dc868e6fcd..77895e0e76 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -80,13 +80,6 @@ public slots:
*/
void raise();
- /**jsdoc
- * Raise the Interface window if it is minimized. If raised, the window gains focus.
- * @function Window.raiseMainWindow
- * @deprecated Use {@link Window.raise|raise} instead.
- */
- void raiseMainWindow();
-
/**jsdoc
* Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
* waiting for a user response.
diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp
index ea660fb0e2..108f20b2dd 100644
--- a/interface/src/ui/ApplicationOverlay.cpp
+++ b/interface/src/ui/ApplicationOverlay.cpp
@@ -179,7 +179,7 @@ static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPT
void ApplicationOverlay::buildFramebufferObject() {
PROFILE_RANGE(app, __FUNCTION__);
- auto uiSize = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
+ auto uiSize = glm::uvec2(qApp->getUiSize());
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ApplicationOverlay"));
}
diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp
index 39a5849d85..9804c6712a 100644
--- a/interface/src/ui/LoginDialog.cpp
+++ b/interface/src/ui/LoginDialog.cpp
@@ -113,9 +113,8 @@ void LoginDialog::linkSteam() {
}
JSONCallbackParameters callbackParams;
- callbackParams.jsonCallbackReceiver = this;
+ callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "linkCompleted";
- callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "linkFailed";
const QString LINK_STEAM_PATH = "api/v1/user/steam/link";
@@ -141,9 +140,8 @@ void LoginDialog::createAccountFromStream(QString username) {
}
JSONCallbackParameters callbackParams;
- callbackParams.jsonCallbackReceiver = this;
+ callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "createCompleted";
- callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "createFailed";
const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create";
@@ -174,39 +172,40 @@ void LoginDialog::openUrl(const QString& url) const {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
newObject->setProperty("url", url);
});
+ LoginDialog::hide();
} else {
if (!hmd->getShouldShowTablet() && !qApp->isHMDMode()) {
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
newObject->setProperty("url", url);
});
+ LoginDialog::hide();
} else {
tablet->gotoWebScreen(url);
}
}
}
-void LoginDialog::linkCompleted(QNetworkReply& reply) {
+void LoginDialog::linkCompleted(QNetworkReply* reply) {
emit handleLinkCompleted();
}
-void LoginDialog::linkFailed(QNetworkReply& reply) {
- emit handleLinkFailed(reply.errorString());
+void LoginDialog::linkFailed(QNetworkReply* reply) {
+ emit handleLinkFailed(reply->errorString());
}
-void LoginDialog::createCompleted(QNetworkReply& reply) {
+void LoginDialog::createCompleted(QNetworkReply* reply) {
emit handleCreateCompleted();
}
-void LoginDialog::createFailed(QNetworkReply& reply) {
- emit handleCreateFailed(reply.errorString());
+void LoginDialog::createFailed(QNetworkReply* reply) {
+ emit handleCreateFailed(reply->errorString());
}
void LoginDialog::signup(const QString& email, const QString& username, const QString& password) {
JSONCallbackParameters callbackParams;
- callbackParams.jsonCallbackReceiver = this;
+ callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "signupCompleted";
- callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "signupFailed";
QJsonObject payload;
@@ -228,7 +227,7 @@ void LoginDialog::signup(const QString& email, const QString& username, const QS
QJsonDocument(payload).toJson());
}
-void LoginDialog::signupCompleted(QNetworkReply& reply) {
+void LoginDialog::signupCompleted(QNetworkReply* reply) {
emit handleSignupCompleted();
}
@@ -242,10 +241,10 @@ QString errorStringFromAPIObject(const QJsonValue& apiObject) {
}
}
-void LoginDialog::signupFailed(QNetworkReply& reply) {
+void LoginDialog::signupFailed(QNetworkReply* reply) {
// parse the returned JSON to see what the problem was
- auto jsonResponse = QJsonDocument::fromJson(reply.readAll());
+ auto jsonResponse = QJsonDocument::fromJson(reply->readAll());
static const QString RESPONSE_DATA_KEY = "data";
diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h
index 5ebf866fbd..ad8cab9699 100644
--- a/interface/src/ui/LoginDialog.h
+++ b/interface/src/ui/LoginDialog.h
@@ -42,14 +42,14 @@ signals:
void handleSignupFailed(QString errorString);
public slots:
- void linkCompleted(QNetworkReply& reply);
- void linkFailed(QNetworkReply& reply);
+ void linkCompleted(QNetworkReply* reply);
+ void linkFailed(QNetworkReply* reply);
- void createCompleted(QNetworkReply& reply);
- void createFailed(QNetworkReply& reply);
+ void createCompleted(QNetworkReply* reply);
+ void createFailed(QNetworkReply* reply);
- void signupCompleted(QNetworkReply& reply);
- void signupFailed(QNetworkReply& reply);
+ void signupCompleted(QNetworkReply* reply);
+ void signupFailed(QNetworkReply* reply);
protected slots:
Q_INVOKABLE bool isSteamRunning() const;
diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp
index d131bb3467..e27001567f 100644
--- a/interface/src/ui/OverlayConductor.cpp
+++ b/interface/src/ui/OverlayConductor.cpp
@@ -88,38 +88,24 @@ void OverlayConductor::update(float dt) {
_hmdMode = false;
}
- bool isAtRest = updateAvatarIsAtRest();
- bool isMoving = !isAtRest;
-
bool shouldRecenter = false;
- if (_flags & SuppressedByMove) {
- if (!isMoving) {
- _flags &= ~SuppressedByMove;
- shouldRecenter = true;
- }
- } else {
- if (myAvatar->getClearOverlayWhenMoving() && isMoving) {
- _flags |= SuppressedByMove;
- }
- }
-
- if (_flags & SuppressedByHead) {
- if (isAtRest) {
- _flags &= ~SuppressedByHead;
+ if (_suppressedByHead) {
+ if (updateAvatarIsAtRest()) {
+ _suppressedByHead = false;
shouldRecenter = true;
}
} else {
if (_hmdMode && headOutsideOverlay()) {
- _flags |= SuppressedByHead;
+ _suppressedByHead = true;
}
}
- bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
+ bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && !_suppressedByHead;
if (targetVisible != currentVisible) {
offscreenUi->setPinned(!targetVisible);
}
- if (shouldRecenter && !_flags) {
+ if (shouldRecenter && !_suppressedByHead) {
centerUI();
}
}
diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h
index cf69c32fc5..b47e23d28a 100644
--- a/interface/src/ui/OverlayConductor.h
+++ b/interface/src/ui/OverlayConductor.h
@@ -25,13 +25,7 @@ private:
bool headOutsideOverlay() const;
bool updateAvatarIsAtRest();
- enum SupressionFlags {
- SuppressedByMove = 0x01,
- SuppressedByHead = 0x02,
- SuppressMask = 0x03,
- };
-
- uint8_t _flags { SuppressedByMove };
+ bool _suppressedByHead { false };
bool _hmdMode { false };
// used by updateAvatarIsAtRest
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index 3d3c432e92..d0fe92ea00 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -161,12 +161,6 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter));
}
- {
- auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); };
- auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
- preferences->addPreference(new CheckPreference(UI_CATEGORY, "Clear overlays when moving", getter, setter));
- }
-
static const QString VIEW_CATEGORY{ "View" };
{
auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); };
@@ -233,6 +227,8 @@ void setupPreferences() {
auto getter = [=]()->float { return myAvatar->getTargetScale(); };
auto setter = [=](float value) { myAvatar->setTargetScale(value); };
auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter);
+ preference->setMin(0.25);
+ preference->setMax(4);
preference->setStep(0.05f);
preference->setDecimals(2);
preferences->addPreference(preference);
@@ -304,22 +300,32 @@ void setupPreferences() {
preference->setStep(0.001f);
preferences->addPreference(preference);
}
+ {
+ auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] {
+ qApp->resetSensors();
+ });
+ preferences->addPreference(preference);
+ }
static const QString AVATAR_CAMERA{ "Mouse Sensitivity" };
{
auto getter = [=]()->float { return myAvatar->getPitchSpeed(); };
auto setter = [=](float value) { myAvatar->setPitchSpeed(value); };
- auto preference = new SpinnerPreference(AVATAR_CAMERA, "Pitch speed (degrees/second)", getter, setter);
+ auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "Y input:", getter, setter);
preference->setMin(1.0f);
preference->setMax(360.0f);
+ preference->setStep(1);
+ preference->setDecimals(1);
preferences->addPreference(preference);
}
{
auto getter = [=]()->float { return myAvatar->getYawSpeed(); };
auto setter = [=](float value) { myAvatar->setYawSpeed(value); };
- auto preference = new SpinnerPreference(AVATAR_CAMERA, "Yaw speed (degrees/second)", getter, setter);
+ auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "X input:", getter, setter);
preference->setMin(1.0f);
preference->setMax(360.0f);
+ preference->setStep(1);
+ preference->setDecimals(1);
preferences->addPreference(preference);
}
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index efcc85b23e..60c039ce1f 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -493,7 +493,7 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) {
multiPart->append(imagePart);
auto accountManager = DependencyManager::get();
- JSONCallbackParameters callbackParams(uploader, "uploadSuccess", uploader, "uploadFailure");
+ JSONCallbackParameters callbackParams(uploader, "uploadSuccess", "uploadFailure");
accountManager->sendRequest(SNAPSHOT_UPLOAD_URL, AccountManagerAuth::Required, QNetworkAccessManager::PostOperation,
callbackParams, nullptr, multiPart);
diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp
index 4613871d25..694d0fa8f8 100644
--- a/interface/src/ui/SnapshotUploader.cpp
+++ b/interface/src/ui/SnapshotUploader.cpp
@@ -23,11 +23,11 @@ SnapshotUploader::SnapshotUploader(QUrl inWorldLocation, QString pathname) :
_pathname(pathname) {
}
-void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
+void SnapshotUploader::uploadSuccess(QNetworkReply* reply) {
const QString STORY_UPLOAD_URL = "/api/v1/user_stories";
// parse the reply for the thumbnail_url
- QByteArray contents = reply.readAll();
+ QByteArray contents = reply->readAll();
QJsonParseError jsonError;
auto doc = QJsonDocument::fromJson(contents, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
@@ -60,7 +60,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
rootObject.insert("user_story", userStoryObject);
auto accountManager = DependencyManager::get();
- JSONCallbackParameters callbackParams(this, "createStorySuccess", this, "createStoryFailure");
+ JSONCallbackParameters callbackParams(this, "createStorySuccess", "createStoryFailure");
accountManager->sendRequest(STORY_UPLOAD_URL,
AccountManagerAuth::Required,
@@ -74,11 +74,11 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
}
}
-void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
- QString replyString = reply.readAll();
- qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation;
+void SnapshotUploader::uploadFailure(QNetworkReply* reply) {
+ QString replyString = reply->readAll();
+ qDebug() << "Error " << reply->errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation;
if (replyString.size() == 0) {
- replyString = reply.errorString();
+ replyString = reply->errorString();
}
replyString = replyString.left(1000); // Only print first 1000 characters of error
qDebug() << "Snapshot upload reply error (truncated):" << replyString;
@@ -86,17 +86,17 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
this->deleteLater();
}
-void SnapshotUploader::createStorySuccess(QNetworkReply& reply) {
- QString replyString = reply.readAll();
+void SnapshotUploader::createStorySuccess(QNetworkReply* reply) {
+ QString replyString = reply->readAll();
emit DependencyManager::get()->snapshotShared(false, replyString);
this->deleteLater();
}
-void SnapshotUploader::createStoryFailure(QNetworkReply& reply) {
- QString replyString = reply.readAll();
- qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation;
+void SnapshotUploader::createStoryFailure(QNetworkReply* reply) {
+ QString replyString = reply->readAll();
+ qDebug() << "Error " << reply->errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation;
if (replyString.size() == 0) {
- replyString = reply.errorString();
+ replyString = reply->errorString();
}
replyString = replyString.left(1000); // Only print first 1000 characters of error
qDebug() << "Snapshot story upload reply error (truncated):" << replyString;
diff --git a/interface/src/ui/SnapshotUploader.h b/interface/src/ui/SnapshotUploader.h
index ae6d5d55ca..d8e72730c7 100644
--- a/interface/src/ui/SnapshotUploader.h
+++ b/interface/src/ui/SnapshotUploader.h
@@ -21,12 +21,12 @@ class SnapshotUploader : public QObject {
public:
SnapshotUploader(QUrl inWorldLocation, QString pathname);
public slots:
- void uploadSuccess(QNetworkReply& reply);
- void uploadFailure(QNetworkReply& reply);
- void createStorySuccess(QNetworkReply& reply);
- void createStoryFailure(QNetworkReply& reply);
+ void uploadSuccess(QNetworkReply* reply);
+ void uploadFailure(QNetworkReply* reply);
+ void createStorySuccess(QNetworkReply* reply);
+ void createStoryFailure(QNetworkReply* reply);
private:
QUrl _inWorldLocation;
QString _pathname;
};
-#endif // hifi_SnapshotUploader_h
\ No newline at end of file
+#endif // hifi_SnapshotUploader_h
diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp
index c7ee868855..6bb615948c 100644
--- a/interface/src/ui/Stats.cpp
+++ b/interface/src/ui/Stats.cpp
@@ -333,7 +333,13 @@ void Stats::updateStats(bool force) {
}
auto gpuContext = qApp->getGPUContext();
-
+ auto displayPlugin = qApp->getActiveDisplayPlugin();
+ if (displayPlugin) {
+ QVector2D dims(displayPlugin->getRecommendedRenderSize().x, displayPlugin->getRecommendedRenderSize().y);
+ dims *= displayPlugin->getRenderResolutionScale();
+ STAT_UPDATE(gpuFrameSize, dims);
+ STAT_UPDATE(gpuFrameTimePerPixel, (float)(gpuContext->getFrameTimerGPUAverage()*1000000.0 / double(dims.x()*dims.y())));
+ }
// Update Frame timing (in ms)
STAT_UPDATE(gpuFrameTime, (float)gpuContext->getFrameTimerGPUAverage());
STAT_UPDATE(batchFrameTime, (float)gpuContext->getFrameTimerBatchAverage());
diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h
index 36e923261d..f4181f9788 100644
--- a/interface/src/ui/Stats.h
+++ b/interface/src/ui/Stats.h
@@ -276,7 +276,9 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, gpuTextureExternalMemory, 0)
STATS_PROPERTY(QString, gpuTextureMemoryPressureState, QString())
STATS_PROPERTY(int, gpuFreeMemory, 0)
+ STATS_PROPERTY(QVector2D, gpuFrameSize, QVector2D(0,0))
STATS_PROPERTY(float, gpuFrameTime, 0)
+ STATS_PROPERTY(float, gpuFrameTimePerPixel, 0)
STATS_PROPERTY(float, batchFrameTime, 0)
STATS_PROPERTY(float, engineFrameTime, 0)
STATS_PROPERTY(float, avatarSimulationTime, 0)
@@ -962,6 +964,20 @@ signals:
*/
void gpuFrameTimeChanged();
+ /**jsdoc
+ * Triggered when the value of the gpuFrameTime
property changes.
+ * @function Stats.gpuFrameTimeChanged
+ * @returns {Signal}
+ */
+ void gpuFrameSizeChanged();
+
+ /**jsdoc
+ * Triggered when the value of the gpuFrameTime
property changes.
+ * @function Stats.gpuFrameTimeChanged
+ * @returns {Signal}
+ */
+ void gpuFrameTimePerPixelChanged();
+
/**jsdoc
* Triggered when the value of the batchFrameTime
property changes.
* @function Stats.batchFrameTimeChanged
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp
index a879dcfada..789b1f9969 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.cpp
+++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp
@@ -97,6 +97,10 @@ static const float CONTEXT_OVERLAY_UNHOVERED_COLORPULSE = 1.0f;
void ContextOverlayInterface::setEnabled(bool enabled) {
_enabled = enabled;
+ if (!enabled) {
+ // Destroy any potentially-active ContextOverlays when disabling the interface
+ createOrDestroyContextOverlay(EntityItemID(), PointerEvent());
+ }
}
void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp
index b303e7f919..7489f6e189 100644
--- a/interface/src/ui/overlays/ModelOverlay.cpp
+++ b/interface/src/ui/overlays/ModelOverlay.cpp
@@ -27,6 +27,12 @@ ModelOverlay::ModelOverlay()
{
_model->setLoadingPriority(_loadPriority);
_isLoaded = false;
+
+ // Don't show overlay until textures have loaded
+ _visible = false;
+
+ render::ScenePointer scene = qApp->getMain3DScene();
+ _model->setVisibleInScene(false, scene);
}
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
@@ -101,10 +107,11 @@ void ModelOverlay::update(float deltatime) {
emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model);
}
bool metaDirty = false;
- if (_visibleDirty) {
+ if (_visibleDirty && _texturesLoaded) {
_visibleDirty = false;
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW);
+
_model->setTagMask(modelRenderTagMask, scene);
_model->setVisibleInScene(getVisible(), scene);
metaDirty = true;
@@ -134,6 +141,8 @@ void ModelOverlay::update(float deltatime) {
if (!_modelTextures.isEmpty()) {
_model->setTextures(_modelTextures);
}
+
+ _model->setVisibleInScene(getVisible(), scene);
_model->updateRenderItems();
}
}
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index a54bc0795e..9a054ffaa3 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -116,7 +116,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
auto geometryCache = DependencyManager::get();
auto textureCache = DependencyManager::get();
- auto size = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
+ auto size = glm::uvec2(qApp->getUiSize());
int width = size.x;
int height = size.y;
mat4 legacyProjection = glm::ortho(0, width, height, 0, -1000, 1000);
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index 08b6927b4b..ec4199a32d 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -42,6 +42,7 @@
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include
+#include
#include
#include "FileDialogHelper.h"
#include "avatar/AvatarManager.h"
@@ -137,11 +138,8 @@ void Web3DOverlay::destroyWebSurface() {
// Fix for crash in QtWebEngineCore when rapidly switching domains
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
if (rootItem) {
- QObject* obj = rootItem->findChild("webEngineView");
- if (obj) {
- // stop loading
- QMetaObject::invokeMethod(obj, "stop");
- }
+ // stop loading
+ QMetaObject::invokeMethod(rootItem, "stop");
}
_webSurface->pause();
@@ -151,6 +149,11 @@ void Web3DOverlay::destroyWebSurface() {
// If the web surface was fetched out of the cache, release it back into the cache
if (_cachedWebSurface) {
+ // If it's going back into the cache make sure to explicitly set the URL to a blank page
+ // in order to stop any resource consumption or audio related to the page.
+ if (rootItem) {
+ rootItem->setProperty("url", "about:blank");
+ }
auto offscreenCache = DependencyManager::get();
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
if (offscreenCache) {
@@ -253,6 +256,7 @@ void Web3DOverlay::setupQmlSurface() {
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data());
_webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
+ _webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get().data());
_webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data());
diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp
index 8cf19b56dd..a8b65492d3 100644
--- a/interface/src/workload/GameWorkloadRenderer.cpp
+++ b/interface/src/workload/GameWorkloadRenderer.cpp
@@ -40,7 +40,6 @@ void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext,
auto visible = _showAllProxies || _showAllViews;
auto showProxies = _showAllProxies;
auto showViews = _showAllViews;
- auto freezeViews = _freezeViews;
render::Transaction transaction;
auto scene = gameWorkloadContext->_scene;
@@ -71,7 +70,7 @@ void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext,
transaction.resetItem(_spaceRenderItemID, std::make_shared(renderItem));
}
- transaction.updateItem(_spaceRenderItemID, [visible, showProxies, proxies, freezeViews, showViews, views](GameWorkloadRenderItem& item) {
+ transaction.updateItem(_spaceRenderItemID, [visible, showProxies, proxies, showViews, views](GameWorkloadRenderItem& item) {
item.setVisible(visible);
item.showProxies(showProxies);
item.setAllProxies(proxies);
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 0833b28142..549989778e 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -1244,7 +1244,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
bool leftArmEnabled, bool rightArmEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
- const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) {
+ const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
+ const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) {
const bool ENABLE_POLE_VECTORS = false;
const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f;
@@ -1271,19 +1272,20 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm");
if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true);
+ glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
// smooth toward desired pole vector from previous pole vector... to reduce jitter
if (!_prevLeftHandPoleVectorValid) {
_prevLeftHandPoleVectorValid = true;
- _prevLeftHandPoleVector = poleVector;
+ _prevLeftHandPoleVector = sensorPoleVector;
}
- glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, poleVector);
+ glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector);
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
_prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector;
_animVars.set("leftHandPoleVectorEnabled", true);
_animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X);
- _animVars.set("leftHandPoleVector", _prevLeftHandPoleVector);
+ _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector));
} else {
_prevLeftHandPoleVectorValid = false;
_animVars.set("leftHandPoleVectorEnabled", false);
@@ -1318,19 +1320,20 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm");
if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false);
+ glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
// smooth toward desired pole vector from previous pole vector... to reduce jitter
if (!_prevRightHandPoleVectorValid) {
_prevRightHandPoleVectorValid = true;
- _prevRightHandPoleVector = poleVector;
+ _prevRightHandPoleVector = sensorPoleVector;
}
- glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, poleVector);
+ glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector);
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR);
_prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector;
_animVars.set("rightHandPoleVectorEnabled", true);
_animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X);
- _animVars.set("rightHandPoleVector", _prevRightHandPoleVector);
+ _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector));
} else {
_prevRightHandPoleVectorValid = false;
_animVars.set("rightHandPoleVectorEnabled", false);
@@ -1345,7 +1348,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
}
}
-void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose) {
+void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose,
+ const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) {
const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f;
@@ -1360,19 +1364,20 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose
int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg");
int upLegJointIndex = _animSkeleton->nameToJointIndex("LeftUpLeg");
glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, leftFootPose);
+ glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
- // smooth toward desired pole vector from previous pole vector... to reduce jitter
+ // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space.
if (!_prevLeftFootPoleVectorValid) {
_prevLeftFootPoleVectorValid = true;
- _prevLeftFootPoleVector = poleVector;
+ _prevLeftFootPoleVector = sensorPoleVector;
}
- glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, poleVector);
+ glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, sensorPoleVector);
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR);
_prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector;
_animVars.set("leftFootPoleVectorEnabled", true);
_animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z);
- _animVars.set("leftFootPoleVector", _prevLeftFootPoleVector);
+ _animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector));
} else {
_animVars.unset("leftFootPosition");
_animVars.unset("leftFootRotation");
@@ -1390,19 +1395,20 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose
int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg");
int upLegJointIndex = _animSkeleton->nameToJointIndex("RightUpLeg");
glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, rightFootPose);
+ glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector);
// smooth toward desired pole vector from previous pole vector... to reduce jitter
if (!_prevRightFootPoleVectorValid) {
_prevRightFootPoleVectorValid = true;
- _prevRightFootPoleVector = poleVector;
+ _prevRightFootPoleVector = sensorPoleVector;
}
- glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, poleVector);
+ glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, sensorPoleVector);
glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR);
_prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector;
_animVars.set("rightFootPoleVectorEnabled", true);
_animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z);
- _animVars.set("rightFootPoleVector", _prevRightFootPoleVector);
+ _animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector));
} else {
_animVars.unset("rightFootPosition");
_animVars.unset("rightFootRotation");
@@ -1434,9 +1440,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat);
- // limit swing rotation of the deltaQuat by a 30 degree cone.
+ // limit swing rotation of the deltaQuat by a 25 degree cone.
// TODO: use swing twist decomposition constraint instead, for off axis rotation clamping.
- const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
+ const float MAX_ANGLE = 25.0f * RADIANS_PER_DEGREE;
if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) {
deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat));
}
@@ -1546,16 +1552,18 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled;
bool leftArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_LeftArm] & (uint8_t)ControllerFlags::Enabled;
bool rightArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_RightArm] & (uint8_t)ControllerFlags::Enabled;
+ glm::mat4 sensorToRigMatrix = glm::inverse(params.rigToSensorMatrix);
updateHead(headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]);
updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, dt,
params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand],
- params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo);
+ params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo,
+ params.rigToSensorMatrix, sensorToRigMatrix);
updateFeet(leftFootEnabled, rightFootEnabled,
- params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]);
-
+ params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot],
+ params.rigToSensorMatrix, sensorToRigMatrix);
if (headEnabled) {
// Blend IK chains toward the joint limit centers, this should stablize head and hand ik.
diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h
index e30b5d655c..ffa3a128b9 100644
--- a/libraries/animation/src/Rig.h
+++ b/libraries/animation/src/Rig.h
@@ -75,6 +75,7 @@ public:
};
struct ControllerParameters {
+ glm::mat4 rigToSensorMatrix;
AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space
uint8_t primaryControllerFlags[NumPrimaryControllerTypes];
AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space
@@ -231,8 +232,10 @@ protected:
bool leftArmEnabled, bool rightArmEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose,
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
- const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo);
- void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose);
+ const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo,
+ const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix);
+ void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose,
+ const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix);
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);
void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const;
@@ -359,16 +362,16 @@ protected:
int _nextStateHandlerId { 0 };
QMutex _stateMutex;
- glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z };
+ glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z }; // sensor space
bool _prevRightFootPoleVectorValid { false };
- glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z };
+ glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space
bool _prevLeftFootPoleVectorValid { false };
- glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z };
+ glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z }; // sensor space
bool _prevRightHandPoleVectorValid { false };
- glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z };
+ glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z }; // sensor space
bool _prevLeftHandPoleVectorValid { false };
int _rigId;
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index a5f79290cd..a6f0416a30 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -1426,6 +1426,8 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) {
// restart the input device
switchInputToAudioDevice(_inputDeviceInfo);
+
+ emit isStereoInputChanged(_isStereoInput);
}
return stereoInputChanged;
@@ -1463,6 +1465,8 @@ void AudioClient::outputFormatChanged() {
}
bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) {
+ Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread");
+
qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]";
bool supportedFormat = false;
@@ -1663,6 +1667,8 @@ void AudioClient::outputNotify() {
}
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) {
+ Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread");
+
qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]";
bool supportedFormat = false;
@@ -2021,7 +2027,7 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca
void AudioClient::startThread() {
- moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
+ moveToNewNamedThread(this, "Audio Thread", [this] { start(); }, QThread::TimeCriticalPriority);
}
void AudioClient::setInputVolume(float volume, bool emitSignal) {
diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h
index 30cbceeb0e..bbfd79d0aa 100644
--- a/libraries/audio/src/AbstractAudioInterface.h
+++ b/libraries/audio/src/AbstractAudioInterface.h
@@ -44,6 +44,9 @@ public slots:
virtual bool setIsStereoInput(bool stereo) = 0;
virtual bool isStereoInput() = 0;
+
+signals:
+ void isStereoInputChanged(bool isStereo);
};
Q_DECLARE_METATYPE(AbstractAudioInterface*)
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 1e4f33a95e..69356cdfaa 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -212,6 +212,8 @@ void Avatar::setTargetScale(float targetScale) {
_targetScale = newValue;
_scaleChanged = usecTimestampNow();
_isAnimatingScale = true;
+
+ emit targetScaleChanged(targetScale);
}
}
@@ -1338,6 +1340,9 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
}
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
+ if (!isMyAvatar()) {
+ createOrb();
+ }
AvatarData::setSkeletonModelURL(skeletonModelURL);
if (QThread::currentThread() == thread()) {
_skeletonModel->setURL(_skeletonModelURL);
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
index 98246330c4..157f7b2ec6 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
@@ -23,7 +23,6 @@
#include
#include
-
#include "Head.h"
#include "SkeletonModel.h"
#include "Rig.h"
@@ -41,7 +40,6 @@ static const float SCALING_RATIO = .05f;
extern const float CHAT_MESSAGE_SCALE;
extern const float CHAT_MESSAGE_HEIGHT;
-
enum ScreenTintLayer {
SCREEN_TINT_BEFORE_LANDSCAPE = 0,
SCREEN_TINT_BEFORE_AVATARS,
@@ -69,7 +67,7 @@ public:
static void setShowNamesAboveHeads(bool show);
explicit Avatar(QThread* thread);
- ~Avatar();
+ virtual ~Avatar();
virtual void instantiableAvatar() = 0;
@@ -109,6 +107,7 @@ public:
float getLODDistance() const;
virtual bool isMyAvatar() const override { return false; }
+ virtual void createOrb() { }
virtual QVector getJointRotations() const override;
using AvatarData::getJointRotation;
@@ -167,8 +166,8 @@ public:
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
- static void renderJointConnectingCone( gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
- float radius1, float radius2, const glm::vec4& color);
+ static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
+ float radius1, float radius2, const glm::vec4& color);
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
@@ -235,7 +234,7 @@ public:
/// Scales a world space position vector relative to the avatar position and scale
/// \param vector position to be scaled. Will store the result
- void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const;
+ void scaleVectorRelativeToPosition(glm::vec3& positionToScale) const;
void slamPosition(const glm::vec3& position);
virtual void updateAttitude(const glm::quat& orientation) override;
@@ -254,7 +253,6 @@ public:
void setPositionViaScript(const glm::vec3& position) override;
void setOrientationViaScript(const glm::quat& orientation) override;
-
/**jsdoc
* @function MyAvatar.getParentID
* @returns {Uuid}
@@ -283,7 +281,6 @@ public:
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex) override;
-
/**jsdoc
* Returns an array of joints, where each joint is an object containing name, index, and parentIndex fields.
* @function MyAvatar.getSkeleton
@@ -349,7 +346,6 @@ public:
// not all subclasses of AvatarData have access to this data.
virtual bool canMeasureEyeHeight() const override { return true; }
-
virtual float getModelScale() const { return _modelScale; }
virtual void setModelScale(float scale) { _modelScale = scale; }
virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getModelScale()); }
@@ -365,6 +361,9 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override;
+signals:
+ void targetScaleChanged(float targetScale);
+
public slots:
// FIXME - these should be migrated to use Pose data instead
@@ -382,7 +381,7 @@ public slots:
/**jsdoc
* Get the rotation of the left palm in world coordinates.
* @function MyAvatar.getLeftPalmRotation
- * @returns {Vec3} The rotation of the left palm in world coordinates.
+ * @returns {Quat} The rotation of the left palm in world coordinates.
* @example Report the rotation of your avatar's left palm.
* print(JSON.stringify(MyAvatar.getLeftPalmRotation()));
*/
@@ -399,7 +398,7 @@ public slots:
/**jsdoc
* Get the rotation of the right palm in world coordinates.
* @function MyAvatar.getRightPalmRotation
- * @returns {Vec3} The rotation of the right palm in world coordinates.
+ * @returns {Quat} The rotation of the right palm in world coordinates.
* @example Report the rotation of your avatar's right palm.
* print(JSON.stringify(MyAvatar.getRightPalmRotation()));
*/
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
index 5800c1404b..bdee6d9147 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
@@ -220,30 +220,44 @@ void Head::calculateMouthShapes(float deltaTime) {
void Head::applyEyelidOffset(glm::quat headOrientation) {
// Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches.
-
- if (disableEyelidAdjustment) {
+ bool isBlinking = (_rightEyeBlinkVelocity != 0.0f && _rightEyeBlinkVelocity != 0.0f);
+ if (disableEyelidAdjustment || isBlinking) {
return;
}
- glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FORWARD, getLookAtPosition() - _eyePosition);
- eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head
- float eyePitch = safeEulerAngles(eyeRotation).x;
+ const float EYE_PITCH_TO_COEFFICIENT = 3.5f; // Empirically determined
+ const float MAX_EYELID_OFFSET = 1.5f;
+ const float BLINK_DOWN_MULTIPLIER = 0.25f;
+ const float OPEN_DOWN_MULTIPLIER = 0.3f;
+ const float BROW_UP_MULTIPLIER = 0.5f;
- const float EYE_PITCH_TO_COEFFICIENT = 1.6f; // Empirically determined
- const float MAX_EYELID_OFFSET = 0.8f; // So that don't fully close eyes when looking way down
- float eyelidOffset = glm::clamp(-eyePitch * EYE_PITCH_TO_COEFFICIENT, -1.0f, MAX_EYELID_OFFSET);
+ glm::vec3 lookAt = glm::normalize(getLookAtPosition() - _eyePosition);
+ glm::vec3 headUp = headOrientation * Vectors::UNIT_Y;
+ float eyePitch = (PI / 2.0f) - acos(glm::dot(lookAt, headUp));
+ float eyelidOffset = glm::clamp(abs(eyePitch * EYE_PITCH_TO_COEFFICIENT), 0.0f, MAX_EYELID_OFFSET);
- for (int i = 0; i < 2; i++) {
- const int LEFT_EYE = 8;
- float eyeCoefficient = _transientBlendshapeCoefficients[i] - _transientBlendshapeCoefficients[LEFT_EYE + i];
- eyeCoefficient = glm::clamp(eyelidOffset + eyeCoefficient * (1.0f - eyelidOffset), -1.0f, 1.0f);
- if (eyeCoefficient > 0.0f) {
- _transientBlendshapeCoefficients[i] = eyeCoefficient;
- _transientBlendshapeCoefficients[LEFT_EYE + i] = 0.0f;
+ float blinkUpCoefficient = -eyelidOffset;
+ float blinkDownCoefficient = BLINK_DOWN_MULTIPLIER * eyelidOffset;
+
+ float openUpCoefficient = eyelidOffset;
+ float openDownCoefficient = OPEN_DOWN_MULTIPLIER * eyelidOffset;
+
+ float browsUpCoefficient = BROW_UP_MULTIPLIER * eyelidOffset;
+ float browsDownCoefficient = 0.0f;
- } else {
- _transientBlendshapeCoefficients[i] = 0.0f;
- _transientBlendshapeCoefficients[LEFT_EYE + i] = -eyeCoefficient;
+ bool isLookingUp = (eyePitch > 0);
+
+ if (isLookingUp) {
+ for (int i = 0; i < 2; i++) {
+ _transientBlendshapeCoefficients[EYE_BLINK_INDICES[i]] = blinkUpCoefficient;
+ _transientBlendshapeCoefficients[EYE_OPEN_INDICES[i]] = openUpCoefficient;
+ _transientBlendshapeCoefficients[BROWS_U_INDICES[i]] = browsUpCoefficient;
+ }
+ } else {
+ for (int i = 0; i < 2; i++) {
+ _transientBlendshapeCoefficients[EYE_BLINK_INDICES[i]] = blinkDownCoefficient;
+ _transientBlendshapeCoefficients[EYE_OPEN_INDICES[i]] = openDownCoefficient;
+ _transientBlendshapeCoefficients[BROWS_U_INDICES[i]] = browsDownCoefficient;
}
}
}
diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
deleted file mode 100644
index 7678c03276..0000000000
--- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// Created by Bradley Austin Davis on 2017/04/27
-// 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 "OtherAvatar.h"
-
-OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) {
- // give the pointer to our head to inherited _headData variable from AvatarData
- _headData = new Head(this);
- _skeletonModel = std::make_shared(this, nullptr);
- _skeletonModel->setLoadingPriority(OTHERAVATAR_LOADING_PRIORITY);
- connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
- connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady);
- connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset);
-}
diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
deleted file mode 100644
index df09d7fd99..0000000000
--- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// Created by Bradley Austin Davis on 2017/04/27
-// 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_OtherAvatar_h
-#define hifi_OtherAvatar_h
-
-#include "Avatar.h"
-
-class OtherAvatar : public Avatar {
-public:
- explicit OtherAvatar(QThread* thread);
- virtual void instantiableAvatar() override {};
-};
-
-#endif // hifi_OtherAvatar_h
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index e5ae7ec999..5e799b8401 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -401,7 +401,6 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale)
public:
-
virtual QString getName() const override { return QString("Avatar:") + _displayName; }
static const QString FRAME_NAME;
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index 974ae92432..174e81bb31 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -89,11 +89,15 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe
return avatar;
}
-AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) {
+AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer,
+ bool& isNew) {
QWriteLocker locker(&_hashLock);
auto avatar = _avatarHash.value(sessionUUID);
if (!avatar) {
avatar = addAvatar(sessionUUID, mixerWeakPointer);
+ isNew = true;
+ } else {
+ isNew = false;
}
return avatar;
}
@@ -125,8 +129,13 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer();
+ bool isNewAvatar;
if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) {
- auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
+ auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar);
+ if (isNewAvatar) {
+ QWriteLocker locker(&_hashLock);
+ _pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar });
+ }
// have the matching (or new) avatar parse the data from the packet
int bytesRead = avatar->parseDataFromBuffer(byteArray);
@@ -157,6 +166,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer
{
QReadLocker locker(&_hashLock);
+ _pendingAvatars.remove(identityUUID);
auto me = _avatarHash.find(EMPTY);
if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) {
// We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an
@@ -168,7 +178,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer
if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) {
// mesh URL for a UUID, find avatar in our list
- auto avatar = newOrExistingAvatar(identityUUID, sendingNode);
+ bool isNewAvatar;
+ auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar);
bool identityChanged = false;
bool displayNameChanged = false;
bool skeletonModelUrlChanged = false;
@@ -189,6 +200,7 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock);
+ _pendingAvatars.remove(sessionUUID);
auto removedAvatar = _avatarHash.take(sessionUUID);
if (removedAvatar) {
diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h
index ef6f7845eb..fd2cd76fbf 100644
--- a/libraries/avatars/src/AvatarHashMap.h
+++ b/libraries/avatars/src/AvatarHashMap.h
@@ -19,6 +19,7 @@
#include
#include
+#include
#include
@@ -145,13 +146,21 @@ protected:
virtual AvatarSharedPointer parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode);
virtual AvatarSharedPointer newSharedAvatar();
virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer);
- AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer);
+ AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer,
+ bool& isNew);
virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock
virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
AvatarHash _avatarHash;
+ struct PendingAvatar {
+ std::chrono::steady_clock::time_point creationTime;
+ int transmits;
+ AvatarSharedPointer avatar;
+ };
+ using AvatarPendingHash = QHash;
+ AvatarPendingHash _pendingAvatars;
mutable QReadWriteLock _hashLock;
private:
diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp
index 4119d7a459..19f5efcd16 100644
--- a/libraries/avatars/src/HeadData.cpp
+++ b/libraries/avatars/src/HeadData.cpp
@@ -17,7 +17,6 @@
#include
#include
-#include
#include
#include
@@ -33,7 +32,7 @@ HeadData::HeadData(AvatarData* owningAvatar) :
_summedBlendshapeCoefficients(QVector(0, 0.0f)),
_owningAvatar(owningAvatar)
{
-
+ computeBlendshapesLookupMap();
}
glm::quat HeadData::getRawOrientation() const {
@@ -71,16 +70,10 @@ void HeadData::setOrientation(const glm::quat& orientation) {
setHeadOrientation(orientation);
}
-//Lazily construct a lookup map from the blendshapes
-static const QMap& getBlendshapesLookupMap() {
- static std::once_flag once;
- static QMap blendshapeLookupMap;
- std::call_once(once, [&] {
- for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
- blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i;
- }
- });
- return blendshapeLookupMap;
+void HeadData::computeBlendshapesLookupMap(){
+ for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
+ _blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i;
+ }
}
int HeadData::getNumSummedBlendshapeCoefficients() const {
@@ -108,11 +101,10 @@ const QVector& HeadData::getSummedBlendshapeCoefficients() {
}
void HeadData::setBlendshape(QString name, float val) {
- const auto& blendshapeLookupMap = getBlendshapesLookupMap();
//Check to see if the named blendshape exists, and then set its value if it does
- auto it = blendshapeLookupMap.find(name);
- if (it != blendshapeLookupMap.end()) {
+ auto it = _blendshapeLookupMap.find(name);
+ if (it != _blendshapeLookupMap.end()) {
if (_blendshapeCoefficients.size() <= it.value()) {
_blendshapeCoefficients.resize(it.value() + 1);
}
@@ -123,6 +115,18 @@ void HeadData::setBlendshape(QString name, float val) {
}
}
+int HeadData::getBlendshapeIndex(const QString& name) {
+ auto it = _blendshapeLookupMap.find(name);
+ int index = it != _blendshapeLookupMap.end() ? it.value() : -1;
+ return index;
+}
+
+void HeadData::getBlendshapeIndices(const std::vector& blendShapeNames, std::vector& indexes) {
+ for (auto& name : blendShapeNames) {
+ indexes.push_back(getBlendshapeIndex(name));
+ }
+}
+
static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation");
static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes");
static const QString JSON_AVATAR_HEAD_LEAN_FORWARD = QStringLiteral("leanForward");
@@ -131,10 +135,9 @@ static const QString JSON_AVATAR_HEAD_LOOKAT = QStringLiteral("lookAt");
QJsonObject HeadData::toJson() const {
QJsonObject headJson;
- const auto& blendshapeLookupMap = getBlendshapesLookupMap();
QJsonObject blendshapesJson;
- for (auto name : blendshapeLookupMap.keys()) {
- auto index = blendshapeLookupMap[name];
+ for (auto name : _blendshapeLookupMap.keys()) {
+ auto index = _blendshapeLookupMap[name];
float value = 0.0f;
if (index < _blendshapeCoefficients.size()) {
value += _blendshapeCoefficients[index];
diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h
index f9c4b52139..6d211da2cd 100644
--- a/libraries/avatars/src/HeadData.h
+++ b/libraries/avatars/src/HeadData.h
@@ -20,6 +20,7 @@
#include
#include
+#include
// degrees
const float MIN_HEAD_YAW = -180.0f;
@@ -55,6 +56,8 @@ public:
void setOrientation(const glm::quat& orientation);
void setBlendshape(QString name, float val);
+ int getBlendshapeIndex(const QString& name);
+ void getBlendshapeIndices(const std::vector& blendShapeNames, std::vector& indexes);
const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
const QVector& getSummedBlendshapeCoefficients();
int getNumSummedBlendshapeCoefficients() const;
@@ -114,6 +117,7 @@ protected:
QVector _blendshapeCoefficients;
QVector _transientBlendshapeCoefficients;
QVector _summedBlendshapeCoefficients;
+ QMap _blendshapeLookupMap;
AvatarData* _owningAvatar;
private:
@@ -122,6 +126,7 @@ private:
HeadData& operator= (const HeadData&);
void setHeadOrientation(const glm::quat& orientation);
+ void computeBlendshapesLookupMap();
};
#endif // hifi_HeadData_h
diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp
index c35e641ffd..a0d5cb0920 100644
--- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp
+++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp
@@ -277,7 +277,7 @@ bool CompositorHelper::getReticleOverDesktop() const {
// as being over the desktop.
if (isHMD()) {
QMutexLocker locker(&_reticleLock);
- glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale();
+ glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize());
static const glm::vec2 minOverlayPosition;
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
@@ -319,7 +319,7 @@ void CompositorHelper::sendFakeMouseEvent() {
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
if (isHMD()) {
- glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale();
+ glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize());
// FIXME don't allow negative mouseExtra
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
glm::vec2 minMouse = vec2(0) - mouseExtra;
diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
index 0d556544bb..9200843cf8 100644
--- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp
@@ -888,7 +888,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
}
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
- auto renderSize = glm::uvec2(glm::vec2(getRecommendedRenderSize()) * getRenderResolutionScale());
+ auto renderSize = glm::uvec2(getRecommendedRenderSize());
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
}
diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp
index 4a75994e12..ccebeaf9cc 100644
--- a/libraries/embedded-webserver/src/HTTPManager.cpp
+++ b/libraries/embedded-webserver/src/HTTPManager.cpp
@@ -48,6 +48,13 @@ void HTTPManager::incomingConnection(qintptr socketDescriptor) {
}
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
+ // Reject paths with embedded NULs
+ if (url.path().contains(QChar(0x00))) {
+ connection->respond(HTTPConnection::StatusCode400, "Embedded NULs not allowed in requests");
+ qCWarning(embeddedwebserver) << "Received a request with embedded NULs";
+ return true;
+ }
+
if (!skipSubHandler && requestHandledByRequestHandler(connection, url)) {
// this request was handled by our request handler object
// so we don't need to attempt to do so in the document root
@@ -57,17 +64,27 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
if (!_documentRoot.isEmpty()) {
// check to see if there is a file to serve from the document root for this path
QString subPath = url.path();
-
+
// remove any slash at the beginning of the path
if (subPath.startsWith('/')) {
subPath.remove(0, 1);
}
-
+
+ QString absoluteDocumentRoot { QFileInfo(_documentRoot).absolutePath() };
QString filePath;
-
- if (QFileInfo(_documentRoot + subPath).isFile()) {
- filePath = _documentRoot + subPath;
- } else if (subPath.size() > 0 && !subPath.endsWith('/')) {
+ QFileInfo pathFileInfo { _documentRoot + subPath };
+ QString absoluteFilePath { pathFileInfo.absoluteFilePath() };
+
+ // The absolute path for this file isn't under the document root
+ if (absoluteFilePath.indexOf(absoluteDocumentRoot) != 0) {
+ qCWarning(embeddedwebserver) << absoluteFilePath << "is outside the document root";
+ connection->respond(HTTPConnection::StatusCode400, "Requested path outside document root");
+ return true;
+ }
+
+ if (pathFileInfo.isFile()) {
+ filePath = absoluteFilePath;
+ } else if (subPath.size() > 0 && !subPath.endsWith('/') && pathFileInfo.isDir()) {
// this could be a directory with a trailing slash
// send a redirect to the path with a slash so we can
QString redirectLocation = '/' + subPath + '/';
@@ -80,6 +97,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader);
+ return true;
}
// if the last thing is a trailing slash then we want to look for index file
@@ -87,8 +105,8 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
- if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
- filePath = _documentRoot + subPath + possibleIndexFilename;
+ if (QFileInfo(absoluteFilePath + possibleIndexFilename).exists()) {
+ filePath = absoluteFilePath + possibleIndexFilename;
break;
}
}
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp
index e8f57ea834..78801df715 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp
@@ -363,12 +363,18 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity
return false;
}
-void EntityRenderer::updateModelTransform() {
+void EntityRenderer::updateModelTransformAndBound() {
bool success = false;
auto newModelTransform = _entity->getTransformToCenter(success);
if (success) {
_modelTransform = newModelTransform;
}
+
+ success = false;
+ auto bound = _entity->getAABox(success);
+ if (success) {
+ _bound = bound;
+ }
}
void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) {
@@ -380,15 +386,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa
}
_prevIsTransparent = transparent;
- bool success = false;
- auto bound = entity->getAABox(success);
- if (success) {
- _bound = bound;
- }
- auto newModelTransform = entity->getTransformToCenter(success);
- if (success) {
- _modelTransform = newModelTransform;
- }
+ updateModelTransformAndBound();
_moving = entity->isMovingRelativeToParent();
_visible = entity->getVisible();
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h
index 40966c4f41..496649eb5f 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableEntityItem.h
@@ -97,7 +97,7 @@ protected:
virtual void doRender(RenderArgs* args) = 0;
bool isFading() const { return _isFading; }
- void updateModelTransform();
+ void updateModelTransformAndBound();
virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; }
inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; }
diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
index a6a6dc05f2..309671f49e 100644
--- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp
@@ -126,7 +126,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi
void* key = (void*)this;
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () {
withWriteLock([&] {
- updateModelTransform();
+ updateModelTransformAndBound();
_renderTransform = getModelTransform();
});
});
diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
index 2e0656ab81..c50b3bd760 100644
--- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
@@ -106,10 +106,8 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_position = entity->getWorldPosition();
_dimensions = entity->getScaledDimensions();
_orientation = entity->getWorldOrientation();
- bool success = false;
- auto newModelTransform = entity->getTransformToCenter(success);
- _renderTransform = success ? newModelTransform : getModelTransform();
-
+ updateModelTransformAndBound();
+ _renderTransform = getModelTransform();
if (_shape == entity::Sphere) {
_renderTransform.postScale(SPHERE_ENTITY_SCALE);
}
diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
index ce4b6d9175..08a3b585e4 100644
--- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp
@@ -70,7 +70,7 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () {
withWriteLock([&] {
_dimensions = entity->getScaledDimensions();
- updateModelTransform();
+ updateModelTransformAndBound();
_renderTransform = getModelTransform();
});
});
diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
index 17d6d58781..793b4aa158 100644
--- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp
@@ -206,7 +206,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
glm::vec2 windowSize = getWindowSize(entity);
_webSurface->resize(QSize(windowSize.x, windowSize.y));
- updateModelTransform();
+ updateModelTransformAndBound();
_renderTransform = getModelTransform();
_renderTransform.postScale(entity->getScaledDimensions());
});
diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp
index 2db85eb7ac..95bdae43b9 100644
--- a/libraries/entities/src/AnimationPropertyGroup.cpp
+++ b/libraries/entities/src/AnimationPropertyGroup.cpp
@@ -22,13 +22,14 @@ const float AnimationPropertyGroup::MAXIMUM_POSSIBLE_FRAME = 100000.0f;
bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) {
return
-
(a._currentFrame == b._currentFrame) &&
(a._running == b._running) &&
(a._loop == b._loop) &&
(a._hold == b._hold) &&
(a._firstFrame == b._firstFrame) &&
(a._lastFrame == b._lastFrame) &&
+ (a._fps == b._fps) &&
+ (a._allowTranslation == b._allowTranslation) &&
(a._url == b._url);
}
@@ -40,6 +41,8 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b
(a._hold != b._hold) ||
(a._firstFrame != b._firstFrame) ||
(a._lastFrame != b._lastFrame) ||
+ (a._fps != b._fps) ||
+ (a._allowTranslation != b._allowTranslation) ||
(a._url != b._url);
}
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 31ec189cf9..8e382fabd4 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -2414,11 +2414,7 @@ bool EntityItem::shouldSuppressLocationEdits() const {
}
// if any of the ancestors are MyAvatar, suppress
- if (isChildOfMyAvatar()) {
- return true;
- }
-
- return false;
+ return isChildOfMyAvatar();
}
QList EntityItem::getActionsOfType(EntityDynamicType typeToGet) const {
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index 14c42a1ac2..d2e40eb527 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -1284,6 +1284,8 @@ bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& poin
EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID));
if (!entity) {
qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID;
+ // There is no entity
+ return false;
}
EntityTypes::EntityType entityType = entity->getType();
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index 25bbe70abf..66dd6adfb5 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -2582,6 +2582,13 @@ bool EntityTree::readFromMap(QVariantMap& map) {
}
}
+ // Zero out the spread values that were fixed in version ParticleEntityFix so they behave the same as before
+ if (contentVersion < (int)EntityVersion::ParticleEntityFix) {
+ properties.setRadiusSpread(0.0f);
+ properties.setAlphaSpread(0.0f);
+ properties.setColorSpread({0, 0, 0});
+ }
+
EntityItemPointer entity = addEntity(entityItemID, properties);
if (!entity) {
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp
index 3ed84035ac..28dc7b26c4 100644
--- a/libraries/entities/src/SimpleEntitySimulation.cpp
+++ b/libraries/entities/src/SimpleEntitySimulation.cpp
@@ -100,7 +100,7 @@ void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
}
} else if (entity->isMovingRelativeToParent()) {
SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity);
- if (itr != _simpleKinematicEntities.end()) {
+ if (itr == _simpleKinematicEntities.end()) {
_simpleKinematicEntities.insert(entity);
entity->setLastSimulated(usecTimestampNow());
}
@@ -118,7 +118,7 @@ void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
if (entity->isMovingRelativeToParent()) {
SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity);
- if (itr != _simpleKinematicEntities.end()) {
+ if (itr == _simpleKinematicEntities.end()) {
_simpleKinematicEntities.insert(entity);
entity->setLastSimulated(usecTimestampNow());
}
diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp
index b830313723..d0efed3df4 100644
--- a/libraries/fbx/src/FBXReader.cpp
+++ b/libraries/fbx/src/FBXReader.cpp
@@ -1816,7 +1816,6 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
}
}
- float clusterScale = extractUniformScale(fbxCluster.inverseBindMatrix);
glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform;
ShapeVertices& points = shapeVertices.at(jointIndex);
@@ -1832,7 +1831,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
if (weight >= EXPANSION_WEIGHT_THRESHOLD) {
// transform to joint-frame and save for later
const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex));
- points.push_back(extractTranslation(vertexTransform) * clusterScale);
+ points.push_back(extractTranslation(vertexTransform));
}
// look for an unused slot in the weights vector
@@ -1886,12 +1885,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
FBXJoint& joint = geometry.joints[jointIndex];
// transform cluster vertices to joint-frame and save for later
- float clusterScale = extractUniformScale(firstFBXCluster.inverseBindMatrix);
glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform;
ShapeVertices& points = shapeVertices.at(jointIndex);
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex);
- points.push_back(extractTranslation(vertexTransform) * clusterScale);
+ points.push_back(extractTranslation(vertexTransform));
}
// Apply geometric offset, if present, by transforming the vertices directly
diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp
index 1fa4b3873e..300e6f4846 100644
--- a/libraries/fbx/src/GLTFReader.cpp
+++ b/libraries/fbx/src/GLTFReader.cpp
@@ -21,14 +21,17 @@
#include
#include
+
#include
#include
#include
+#include
#include
#include
#include