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..5d71d8bdb2 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml @@ -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..ae50a746b6 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -242,6 +242,56 @@ Rectangle { } + Item { + id: statusButtonContainer + anchors.verticalCenter: parent.verticalCenter + anchors.left: outputDeviceButtonContainer.right + anchors.leftMargin: 2 + width: 32 + height: width + + Rectangle { + id: statusButton + property string currentStatus: "" + anchors.centerIn: parent + width: 20 + height: width + radius: width/2 + visible: false + } + + ColorOverlay { + anchors.fill: statusButton + opacity: statusButtonMouseArea.containsMouse ? 1.0 : 0.7 + source: statusButton + color: if (statusButton.currentStatus === "busy") { + "red" + } else if (statusButton.currentStatus === "available") { + "green" + } else { + "yellow" + } + } + + MouseArea { + id: statusButtonMouseArea + anchors.fill: parent + hoverEnabled: true + onEntered: { + Tablet.playSound(TabletEnums.ButtonHover); + } + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + + sendToScript({ + "source": "SimplifiedTopBar.qml", + "method": "toggleStatus" + }); + } + } + } + + Item { id: hmdButtonContainer @@ -401,6 +451,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/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/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..f208fcaf1e 100644 --- a/scripts/system/simplifiedUI/simplifiedUI.js +++ b/scripts/system/simplifiedUI/simplifiedUI.js @@ -276,6 +276,11 @@ function setOutputMuted(outputMuted) { } +function toggleStatus() { + +} + + var TOP_BAR_MESSAGE_SOURCE = "SimplifiedTopBar.qml"; function onMessageFromTopBar(message) { if (message.source !== TOP_BAR_MESSAGE_SOURCE) { @@ -295,6 +300,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; @@ -435,7 +444,25 @@ function ensureFirstPersonCameraInHMD(isHMDMode) { } } + +function onStatusChanged() { + var currentStatus = si.getLocalStatus(); + + if (topBarWindow) { + topBarWindow.sendToQml({ + "source": "simplifiedUI.js", + "method": "updateStatusButton", + "data": { + "currentStatus": currentStatus + } + }); + } +} + + 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 +483,10 @@ function startup() { loadSimplifiedTopBar(); simplifiedNametag.create(); + si = new SimplifiedStatusIndicator({ + statusChanged: onStatusChanged + }); + si.startup(); updateInputDeviceMutedOverlay(Audio.muted); updateOutputDeviceMutedOverlay(isOutputMuted()); Audio.mutedDesktopChanged.connect(onDesktopInputDeviceMutedChanged); @@ -506,6 +537,7 @@ function shutdown() { maybeDeleteOutputDeviceMutedOverlay(); simplifiedNametag.destroy(); + si.unload(); Audio.mutedDesktopChanged.disconnect(onDesktopInputDeviceMutedChanged); Window.geometryChanged.disconnect(onGeometryChanged);