diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml
index 840a2bb69a..da924615e3 100644
--- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml
@@ -19,8 +19,8 @@ Flickable {
id: root
contentWidth: parent.width
contentHeight: audioColumnLayout.height
- topMargin: 16
- bottomMargin: 16
+ topMargin: 24
+ bottomMargin: 24
clip: true
function changePeakValuesEnabled(enabled) {
@@ -189,7 +189,7 @@ Flickable {
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
- spacing: 4
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
clip: true
model: AudioScriptingInterface.devices.input
delegate: Item {
@@ -200,6 +200,8 @@ Flickable {
id: inputDeviceCheckbox
anchors.left: parent.left
width: parent.width - inputLevel.width
+ height: paintedHeight
+ wrapLabel: false
checked: selectedDesktop
text: model.devicename
ButtonGroup.group: inputDeviceButtonGroup
@@ -288,7 +290,7 @@ Flickable {
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
- spacing: 4
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
clip: true
model: AudioScriptingInterface.devices.output
delegate: Item {
@@ -299,8 +301,10 @@ Flickable {
id: outputDeviceCheckbox
anchors.left: parent.left
width: parent.width
+ height: paintedHeight
checked: selectedDesktop
text: model.devicename
+ wrapLabel: false
ButtonGroup.group: outputDeviceButtonGroup
onClicked: {
AudioScriptingInterface.setOutputDevice(model.info, false); // `false` argument for Desktop mode setting
diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml
index de5e75b7e5..e32890a2dd 100644
--- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml
@@ -20,8 +20,8 @@ Flickable {
id: root
contentWidth: parent.width
contentHeight: generalColumnLayout.height
- topMargin: 16
- bottomMargin: 16
+ topMargin: 24
+ bottomMargin: 24
clip: true
onAvatarNametagModeChanged: {
@@ -63,6 +63,7 @@ Flickable {
ColumnLayout {
id: avatarNameTagsRadioButtonGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
SimplifiedControls.RadioButton {
id: avatarNameTagsOff
@@ -110,6 +111,7 @@ Flickable {
ColumnLayout {
id: performanceRadioButtonGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
SimplifiedControls.RadioButton {
id: performanceLow
@@ -157,6 +159,7 @@ Flickable {
ColumnLayout {
id: cameraRadioButtonGroup
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
SimplifiedControls.RadioButton {
id: firstPerson
diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml
index a462af0722..96dbc5e180 100644
--- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml
@@ -19,8 +19,8 @@ Flickable {
id: root
contentWidth: parent.width
contentHeight: vrColumnLayout.height
- topMargin: 16
- bottomMargin: 16
+ topMargin: 24
+ bottomMargin: 24
clip: true
function changePeakValuesEnabled(enabled) {
@@ -70,6 +70,7 @@ Flickable {
id: controlsRadioButtonGroup
width: parent.width
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
ButtonGroup { id: controlsButtonGroup }
@@ -202,7 +203,7 @@ Flickable {
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
- spacing: 4
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
clip: true
model: AudioScriptingInterface.devices.input
delegate: Item {
@@ -301,7 +302,7 @@ Flickable {
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
interactive: false
height: contentItem.height
- spacing: 4
+ spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
clip: true
model: AudioScriptingInterface.devices.output
delegate: Item {
diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml
index 5ccc1a7e5c..752c989500 100644
--- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml
@@ -147,7 +147,7 @@ QtObject {
}
readonly property color darkSeparator: "#595959"
- readonly property color darkBackground: "#1A1A1A"
+ readonly property color darkBackground: "#000000"
readonly property color darkBackgroundHighlight: "#575757"
readonly property color highlightOnDark: Qt.rgba(1, 1, 1, 0.2)
readonly property color white: "#FFFFFF"
@@ -182,9 +182,10 @@ QtObject {
}
readonly property QtObject settings: QtObject {
- property real subtitleTopMargin: 2
- property real settingsGroupTopMargin: 10
- property real spacingBetweenSettings: 48
+ property int subtitleTopMargin: 2
+ property int settingsGroupTopMargin: 24
+ property int spacingBetweenSettings: 48
+ property int spacingBetweenRadiobuttons: 14
}
}
diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml
index 59c4fa26e4..43d4aaee33 100644
--- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml
@@ -87,7 +87,6 @@ RadioButton {
contentItem: Text {
id: radioButtonLabel
- height: root.radioButtonRadius
font.pixelSize: 14
font.family: "Graphik"
font.weight: Font.Normal
@@ -99,5 +98,6 @@ RadioButton {
enabled: root.enabled
verticalAlignment: Text.AlignVCenter
leftPadding: radioButtonIndicator.width + root.labelLeftMargin
+ topPadding: -3 // For perfect alignment when using Graphik
}
}
diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
index 09873396fa..c0511c69a4 100644
--- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
@@ -242,6 +242,69 @@ Rectangle {
}
+ Item {
+ id: statusButtonContainer
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: outputDeviceButtonContainer.right
+ anchors.leftMargin: 8
+ width: 36
+ height: width
+
+ Rectangle {
+ id: statusButton
+ property string currentStatus
+ anchors.centerIn: parent
+ anchors.horizontalCenterOffset: 1
+ anchors.verticalCenterOffset: 2
+ width: 13
+ height: width
+ radius: width/2
+ visible: false
+ }
+
+ ColorOverlay {
+ anchors.fill: statusButton
+ opacity: statusButton.currentStatus ? 1 : 0
+ source: statusButton
+ color: if (statusButton.currentStatus === "busy") {
+ "#ff001a"
+ } else if (statusButton.currentStatus === "available") {
+ "#009036"
+ } else if (statusButton.currentStatus) {
+ "#ffed00"
+ }
+ }
+
+ Image {
+ id: focusIcon
+ source: "./images/focus.svg"
+ opacity: statusButtonMouseArea.containsMouse ? 1.0 : (statusButton.currentStatus === "busy" ? 0.7 : 0.3)
+ anchors.centerIn: parent
+ width: 36
+ height: 20
+ fillMode: Image.PreserveAspectFit
+ }
+
+ MouseArea {
+ id: statusButtonMouseArea
+ anchors.fill: parent
+ enabled: statusButton.currentStatus
+ hoverEnabled: true
+ onEntered: {
+ Tablet.playSound(TabletEnums.ButtonHover);
+ }
+ onClicked: {
+ Tablet.playSound(TabletEnums.ButtonClick);
+
+ sendToScript({
+ "source": "SimplifiedTopBar.qml",
+ "method": "toggleStatus"
+ });
+ }
+ }
+ }
+
+
Item {
id: hmdButtonContainer
@@ -280,11 +343,6 @@ Rectangle {
Tablet.playSound(TabletEnums.ButtonClick);
var displayPluginCount = Window.getDisplayPluginCount();
if (HMD.active) {
- // This next line seems backwards and shouldn't be necessary - the NOTIFY handler should
- // result in `displayModeImage.source` changing automatically - but that's not working.
- // This is working. So, I'm keeping it.
- displayModeImage.source = "./images/vrMode.svg";
-
// Switch to desktop mode - selects first VR display plugin
for (var i = 0; i < displayPluginCount; i++) {
if (!Window.isDisplayPluginHmd(i)) {
@@ -293,11 +351,6 @@ Rectangle {
}
}
} else {
- // This next line seems backwards and shouldn't be necessary - the NOTIFY handler should
- // result in `displayModeImage.source` changing automatically - but that's not working.
- // This is working. So, I'm keeping it.
- displayModeImage.source = "./images/desktopMode.svg";
-
// Switch to VR mode - selects first HMD display plugin
for (var i = 0; i < displayPluginCount; i++) {
if (Window.isDisplayPluginHmd(i)) {
@@ -401,6 +454,10 @@ Rectangle {
outputDeviceButton.outputMuted = message.data.outputMuted;
break;
+ case "updateStatusButton":
+ statusButton.currentStatus = message.data.currentStatus;
+ break;
+
default:
console.log('SimplifiedTopBar.qml: Unrecognized message from JS');
break;
diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/focus.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/focus.svg
new file mode 100644
index 0000000000..f7950650c6
--- /dev/null
+++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/focus.svg
@@ -0,0 +1,13 @@
+
+
diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg
new file mode 100644
index 0000000000..ebd844c471
--- /dev/null
+++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg
@@ -0,0 +1,3 @@
+
diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h
index c1253f825f..de6141c9d8 100644
--- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h
+++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h
@@ -15,7 +15,7 @@
// These properties have JSDoc documentation in HMDScriptingInterface.h.
class AbstractHMDScriptingInterface : public QObject {
Q_OBJECT
- Q_PROPERTY(bool active READ isHMDMode NOTIFY mountedChanged)
+ Q_PROPERTY(bool active READ isHMDMode NOTIFY displayModeChanged)
Q_PROPERTY(float ipd READ getIPD)
Q_PROPERTY(float eyeHeight READ getEyeHeight)
Q_PROPERTY(float playerHeight READ getPlayerHeight)
diff --git a/scripts/system/simplifiedUI/simplifiedStatusIndicator/simplifiedStatusIndicator.js b/scripts/system/simplifiedUI/simplifiedStatusIndicator/simplifiedStatusIndicator.js
new file mode 100644
index 0000000000..a56e9f3a24
--- /dev/null
+++ b/scripts/system/simplifiedUI/simplifiedStatusIndicator/simplifiedStatusIndicator.js
@@ -0,0 +1,244 @@
+//
+// simplifiedStatusIndicator.js
+//
+// Created by Robin Wilson on 2019-05-17
+// Copyright 2019 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
+
+
+function simplifiedStatusIndicator(properties) {
+ var that = this;
+ var DEBUG = false;
+
+ // #region HEARTBEAT
+
+ // Send heartbeat with updates to database
+ // When this stops, the database will set status to offline
+ var HEARTBEAT_TIMEOUT_MS = 5000,
+ heartbeat;
+ function startHeartbeatTimer() {
+ if (heartbeat) {
+ Script.clearTimeout(heartbeat);
+ heartbeat = false;
+ }
+
+ heartbeat = Script.setTimeout(function() {
+ heartbeat = false;
+ getStatus(setStatus);
+ }, HEARTBEAT_TIMEOUT_MS);
+ }
+
+ // #endregion HEARTBEAT
+
+
+ // #region SEND/GET STATUS REQUEST
+
+ function setStatusExternally(newStatus) {
+ if (!newStatus) {
+ return;
+ }
+
+ setStatus(newStatus);
+ }
+
+
+ var request = Script.require('request').request,
+ REQUEST_URL = "https://highfidelity.co/api/statusIndicator/";
+ function setStatus(newStatus) {
+ if (heartbeat) {
+ Script.clearTimeout(heartbeat);
+ heartbeat = false;
+ }
+
+ if (newStatus && currentStatus !== newStatus) {
+ currentStatus = newStatus;
+ that.statusChanged();
+ }
+
+ var displayNameToSend = MyAvatar.sessionDisplayName;
+ queryParamString += currentStatus;
+ if (displayNameToSend === "") {
+ displayNameToSend = MyAvatar.displayName;
+ }
+
+ var queryParamString = "type=heartbeat";
+ queryParamString += "&username=" + AccountServices.username;
+ queryParamString += "&displayName=" + displayNameToSend;
+ queryParamString += "&status=";
+ queryParamString += currentStatus;
+
+ var uri = REQUEST_URL + "?" + queryParamString;
+
+ if (DEBUG) {
+ console.log("setStatus: " + uri);
+ }
+
+ request({
+ uri: uri
+ }, function (error, response) {
+ startHeartbeatTimer();
+
+ if (error || !response || response.status !== "success") {
+ console.error("Error with setStatus: " + JSON.stringify(response));
+ return;
+ }
+ });
+ }
+
+ // Get status from database
+ function getStatus(callback) {
+ var queryParamString = "type=getStatus";
+ queryParamString += "&username=" + AccountServices.username;
+
+ var uri = REQUEST_URL + "?" + queryParamString;
+
+ if (DEBUG) {
+ console.log("getStatus: " + uri);
+ }
+
+ request({
+ uri: uri
+ }, function (error, response) {
+ if (error || !response || response.status !== "success") {
+ console.error("Error with getStatus: " + JSON.stringify(response));
+ } else if (response.data.userStatus.toLowerCase() !== "offline") {
+ if (response.data.userStatus !== currentStatus) {
+ currentStatus = response.data.userStatus;
+ that.statusChanged();
+ }
+ }
+
+ if (callback) {
+ callback();
+ }
+ });
+ }
+
+
+ function getLocalStatus() {
+ return currentStatus;
+ }
+
+ // #endregion SEND/GET STATUS REQUEST
+
+
+ // #region SIGNALS
+
+ var currentStatus = "available"; // Default is available
+ function toggleStatus() {
+ if (currentStatus === "busy") {
+ currentStatus = "available";
+ // Else if current status is "available" OR anything else (custom)
+ } else {
+ currentStatus = "busy";
+ }
+
+ that.statusChanged();
+
+ setStatus();
+ }
+
+
+ // When avatar becomes active from being away
+ // Set status back to previousStatus
+ function onWentActive() {
+ if (currentStatus !== previousStatus) {
+ currentStatus = previousStatus;
+ that.statusChanged();
+ }
+ setStatus();
+ }
+
+
+ // When avatar goes away, set status to busy
+ var previousStatus;
+ function onWentAway() {
+ previousStatus = currentStatus;
+ if (currentStatus !== "busy") {
+ currentStatus = "busy";
+ that.statusChanged();
+ }
+ setStatus();
+ }
+
+
+ // Domain changed update avatar location
+ function onDomainChanged() {
+ var queryParamString = "type=updateEmployee";
+ queryParamString += "&username=" + AccountServices.username;
+ queryParamString += "&location=unknown";
+
+ var uri = REQUEST_URL + "?" + queryParamString;
+
+ if (DEBUG) {
+ console.log("simplifiedStatusIndicator onDomainChanged: " + uri);
+ }
+
+ request({
+ uri: uri
+ }, function (error, response) {
+ if (error || !response || response.status !== "success") {
+ console.error("Error with onDomainChanged: " + JSON.stringify(response));
+ } else {
+ // successfully sent updateLocation
+ if (DEBUG) {
+ console.log("Successfully updated location after domain change.");
+ }
+ }
+ });
+ }
+
+
+ function statusChanged() {
+
+ }
+
+ // #endregion SIGNALS
+
+
+ // #region APP LIFETIME
+
+ // Creates the app button and sets up signals and hearbeat
+ function startup() {
+ MyAvatar.wentAway.connect(onWentAway);
+ MyAvatar.wentActive.connect(onWentActive);
+ MyAvatar.displayNameChanged.connect(setStatus);
+ Window.domainChanged.connect(onDomainChanged);
+
+ getStatus(setStatus);
+ }
+
+
+ // Cleans up timeouts, signals, and overlays
+ function unload() {
+ MyAvatar.wentAway.disconnect(onWentAway);
+ MyAvatar.wentActive.disconnect(onWentActive);
+ MyAvatar.displayNameChanged.disconnect(setStatus);
+ Window.domainChanged.disconnect(onDomainChanged);
+ if (heartbeat) {
+ Script.clearTimeout(heartbeat);
+ heartbeat = false;
+ }
+ }
+
+ // #endregion APP LIFETIME
+
+ that.startup = startup;
+ that.unload = unload;
+ that.toggleStatus = toggleStatus;
+ that.setStatus = setStatus;
+ that.getLocalStatus = getLocalStatus;
+ that.statusChanged = statusChanged;
+
+ // Overwrite with the given properties
+ var overwriteableKeys = ["statusChanged"];
+ Object.keys(properties).forEach(function (key) {
+ if (overwriteableKeys.indexOf(key) > -1) {
+ that[key] = properties[key];
+ }
+ });
+}
+
+module.exports = simplifiedStatusIndicator;
\ No newline at end of file
diff --git a/scripts/system/simplifiedUI/simplifiedUI.js b/scripts/system/simplifiedUI/simplifiedUI.js
index c3556a1fbf..fbf9972fc3 100644
--- a/scripts/system/simplifiedUI/simplifiedUI.js
+++ b/scripts/system/simplifiedUI/simplifiedUI.js
@@ -276,6 +276,24 @@ function setOutputMuted(outputMuted) {
}
+var WAIT_FOR_TOP_BAR_MS = 1000;
+function sendLocalStatusToQml() {
+ var currentStatus = si.getLocalStatus();
+
+ if (topBarWindow && currentStatus) {
+ topBarWindow.sendToQml({
+ "source": "simplifiedUI.js",
+ "method": "updateStatusButton",
+ "data": {
+ "currentStatus": currentStatus
+ }
+ });
+ } else {
+ Script.setTimeout(sendLocalStatusToQml, WAIT_FOR_TOP_BAR_MS);
+ }
+}
+
+
var TOP_BAR_MESSAGE_SOURCE = "SimplifiedTopBar.qml";
function onMessageFromTopBar(message) {
if (message.source !== TOP_BAR_MESSAGE_SOURCE) {
@@ -295,6 +313,10 @@ function onMessageFromTopBar(message) {
setOutputMuted(message.data.outputMuted);
break;
+ case "toggleStatus":
+ si.toggleStatus();
+ break;
+
default:
console.log("Unrecognized message from " + TOP_BAR_MESSAGE_SOURCE + ": " + JSON.stringify(message));
break;
@@ -346,13 +368,19 @@ function loadSimplifiedTopBar() {
topBarWindow.fromQml.connect(onMessageFromTopBar);
topBarWindow.closed.connect(onTopBarClosed);
- topBarWindow.sendToQml({
- "source": "simplifiedUI.js",
- "method": "updateOutputMuted",
- "data": {
- "outputMuted": isOutputMuted()
- }
- })
+ // The eventbridge takes a nonzero time to initialize, so we have to wait a bit
+ // for the QML to load and for that to happen before updating the UI.
+ Script.setTimeout(function() {
+ topBarWindow.sendToQml({
+ "source": "simplifiedUI.js",
+ "method": "updateOutputMuted",
+ "data": {
+ "outputMuted": isOutputMuted()
+ }
+ });
+
+ sendLocalStatusToQml();
+ }, WAIT_FOR_TOP_BAR_MS);
}
@@ -435,7 +463,15 @@ function ensureFirstPersonCameraInHMD(isHMDMode) {
}
}
+
+function onStatusChanged() {
+ sendLocalStatusToQml();
+}
+
+
var simplifiedNametag = Script.require("./simplifiedNametag/simplifiedNametag.js");
+var SimplifiedStatusIndicator = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js");
+var si;
var oldShowAudioTools;
var oldShowBubbleTools;
var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false);
@@ -456,6 +492,10 @@ function startup() {
loadSimplifiedTopBar();
simplifiedNametag.create();
+ si = new SimplifiedStatusIndicator({
+ statusChanged: onStatusChanged
+ });
+ si.startup();
updateInputDeviceMutedOverlay(Audio.muted);
updateOutputDeviceMutedOverlay(isOutputMuted());
Audio.mutedDesktopChanged.connect(onDesktopInputDeviceMutedChanged);
@@ -506,6 +546,7 @@ function shutdown() {
maybeDeleteOutputDeviceMutedOverlay();
simplifiedNametag.destroy();
+ si.unload();
Audio.mutedDesktopChanged.disconnect(onDesktopInputDeviceMutedChanged);
Window.geometryChanged.disconnect(onGeometryChanged);