From 0def39dbaac3debd75877a65f3ff4a6ba7c8f324 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 18:14:56 -0800 Subject: [PATCH 01/78] adding user speaking level rotated vertically and muted symbol --- interface/resources/qml/hifi/audio/MicBar.qml | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f51da9c381..51ddfb7f3e 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -13,13 +13,14 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import stylesUit 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { HifiConstants { id: hifi; } readonly property var level: AudioScriptingInterface.inputLevel; - + readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); @@ -29,8 +30,8 @@ Rectangle { property bool standalone: false; property var dragTarget: null; - width: 240; - height: 50; + width: 44; + height: 44; radius: 5; @@ -43,8 +44,8 @@ Rectangle { // borders are painted over fill, so reduce the fill to fit inside the border Rectangle { color: standalone ? colors.fill : "#00000000"; - width: 236; - height: 46; + width: 40; + height: 40; radius: 5; @@ -101,7 +102,6 @@ Rectangle { anchors { left: parent.left; - leftMargin: 5; verticalCenter: parent.verticalCenter; } @@ -117,11 +117,11 @@ Rectangle { id: image; source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; - width: 30; - height: 30; + width: 21; + height: 24; anchors { left: parent.left; - leftMargin: 5; + leftMargin: 7; top: parent.top; topMargin: 5; } @@ -138,20 +138,20 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: colors.muted; - visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || (AudioScriptingInterface.muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - leftMargin: 50; - verticalCenter: parent.verticalCenter; + top: parent.bottom + topMargin: 5 } - width: 170; + width: icon.width; height: 8 - Text { + RalewaySemiBold { anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; @@ -189,11 +189,14 @@ Rectangle { Item { id: bar; - visible: !status.visible; + anchors { + right: parent.right; + rightMargin: 7; + verticalCenter: parent.verticalCenter; + } - anchors.fill: status; - - width: status.width; + width: 8; + height: 32; Rectangle { // base radius: 4; @@ -203,13 +206,12 @@ Rectangle { Rectangle { // mask id: mask; - width: gated ? 0 : parent.width * level; + height: parent.height * level; + width: parent.width; radius: 5; anchors { bottom: parent.bottom; bottomMargin: 0; - top: parent.top; - topMargin: 0; left: parent.left; leftMargin: 0; } @@ -219,10 +221,11 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(170, 0); + end: Qt.point(0, bar.height); + rotation: 180 gradient: Gradient { GradientStop { - position: 0; + position: 0.0; color: colors.greenStart; } GradientStop { @@ -230,8 +233,8 @@ Rectangle { color: colors.greenEnd; } GradientStop { - position: 1; - color: colors.yellow; + position: 1.0; + color: colors.red; } } } From 1ba366c0d7074bec21df042f743b941f0dfbcd55 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 18:19:23 -0800 Subject: [PATCH 02/78] moving file as MicBarApplication --- interface/resources/qml/hifi/audio/MicBar.qml | 67 +++-- .../qml/hifi/audio/MicBarApplication.qml | 241 ++++++++++++++++++ 2 files changed, 273 insertions(+), 35 deletions(-) create mode 100644 interface/resources/qml/hifi/audio/MicBarApplication.qml diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 51ddfb7f3e..7ed9451a04 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -13,25 +13,24 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import stylesUit 1.0 -import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { HifiConstants { id: hifi; } readonly property var level: AudioScriptingInterface.inputLevel; - readonly property var userSpeakingLevel: 0.4; + property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } - + property bool standalone: false; property var dragTarget: null; - width: 44; - height: 44; + width: 240; + height: 50; radius: 5; @@ -44,8 +43,8 @@ Rectangle { // borders are painted over fill, so reduce the fill to fit inside the border Rectangle { color: standalone ? colors.fill : "#00000000"; - width: 40; - height: 40; + width: 236; + height: 46; radius: 5; @@ -102,6 +101,7 @@ Rectangle { anchors { left: parent.left; + leftMargin: 5; verticalCenter: parent.verticalCenter; } @@ -117,11 +117,11 @@ Rectangle { id: image; source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; - width: 21; - height: 24; + width: 30; + height: 30; anchors { left: parent.left; - leftMargin: 7; + leftMargin: 5; top: parent.top; topMargin: 5; } @@ -138,20 +138,20 @@ Rectangle { Item { id: status; - readonly property string color: colors.muted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || (AudioScriptingInterface.muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - top: parent.bottom - topMargin: 5 + leftMargin: 50; + verticalCenter: parent.verticalCenter; } - width: icon.width; + width: 170; height: 8 - RalewaySemiBold { + Text { anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; @@ -189,14 +189,11 @@ Rectangle { Item { id: bar; - anchors { - right: parent.right; - rightMargin: 7; - verticalCenter: parent.verticalCenter; - } + visible: !status.visible; - width: 8; - height: 32; + anchors.fill: status; + + width: status.width; Rectangle { // base radius: 4; @@ -206,12 +203,13 @@ Rectangle { Rectangle { // mask id: mask; - height: parent.height * level; - width: parent.width; + width: gated ? 0 : parent.width * level; radius: 5; anchors { bottom: parent.bottom; bottomMargin: 0; + top: parent.top; + topMargin: 0; left: parent.left; leftMargin: 0; } @@ -221,11 +219,10 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(0, bar.height); - rotation: 180 + end: Qt.point(170, 0); gradient: Gradient { GradientStop { - position: 0.0; + position: 0; color: colors.greenStart; } GradientStop { @@ -233,17 +230,17 @@ Rectangle { color: colors.greenEnd; } GradientStop { - position: 1.0; - color: colors.red; + position: 1; + color: colors.yellow; } } } - + Rectangle { id: gatedIndicator; visible: gated && !AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: "#0080FF"; @@ -252,12 +249,12 @@ Rectangle { verticalCenter: parent.verticalCenter; } } - + Rectangle { id: clippingIndicator; visible: AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: colors.red; diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml new file mode 100644 index 0000000000..33824a3d86 --- /dev/null +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -0,0 +1,241 @@ +// +// MicBar.qml +// qml/hifi/audio +// +// Created by Zach Pomerantz on 6/14/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.5 +import QtGraphicalEffects 1.0 + +import stylesUit 1.0 +import TabletScriptingInterface 1.0 + +Rectangle { + readonly property var level: AudioScriptingInterface.inputLevel; + readonly property var userSpeakingLevel: 0.4; + property bool gated: false; + Component.onCompleted: { + AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); + AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + } + + property bool standalone: false; + property var dragTarget: null; + + width: 44; + height: 44; + + radius: 5; + + color: "#00000000"; + border { + width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; + color: colors.border; + } + + // borders are painted over fill, so reduce the fill to fit inside the border + Rectangle { + color: standalone ? colors.fill : "#00000000"; + width: 40; + height: 40; + + radius: 5; + + anchors { + verticalCenter: parent.verticalCenter; + horizontalCenter: parent.horizontalCenter; + } + } + + MouseArea { + id: mouseArea; + + anchors { + left: icon.left; + right: bar.right; + top: icon.top; + bottom: icon.bottom; + } + + hoverEnabled: true; + scrollGestureEnabled: false; + onClicked: { + AudioScriptingInterface.muted = !AudioScriptingInterface.muted; + Tablet.playSound(TabletEnums.ButtonClick); + } + drag.target: dragTarget; + onContainsMouseChanged: { + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + } + + QtObject { + id: colors; + + readonly property string unmuted: "#FFF"; + readonly property string muted: "#E2334D"; + readonly property string gutter: "#575757"; + readonly property string greenStart: "#39A38F"; + readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; + readonly property string red: colors.muted; + readonly property string fill: "#55000000"; + readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; + readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; + } + + Item { + id: icon; + + anchors { + left: parent.left; + verticalCenter: parent.verticalCenter; + } + + width: 40; + height: 40; + + Item { + Image { + readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; + readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + + id: image; + source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + + width: 21; + height: 24; + anchors { + left: parent.left; + leftMargin: 7; + top: parent.top; + topMargin: 5; + } + } + + ColorOverlay { + anchors { fill: image } + source: image; + color: colors.icon; + } + } + } + + Item { + id: status; + + readonly property string color: colors.muted; + + visible: AudioScriptingInterface.muted && (level >= userSpeakingLevel); + + anchors { + left: parent.left; + top: parent.bottom + topMargin: 5 + } + + width: icon.width; + height: 8 + + RalewaySemiBold { + anchors { + horizontalCenter: parent.horizontalCenter; + verticalCenter: parent.verticalCenter; + } + + color: parent.color; + + text: "MUTED"; + size: 12; + } + } + + Item { + id: bar; + + anchors { + right: parent.right; + rightMargin: 7; + verticalCenter: parent.verticalCenter; + } + + width: 8; + height: 32; + + Rectangle { // base + radius: 4; + anchors { fill: parent } + color: colors.gutter; + } + + Rectangle { // mask + id: mask; + height: parent.height * level; + width: parent.width; + radius: 5; + anchors { + bottom: parent.bottom; + bottomMargin: 0; + left: parent.left; + leftMargin: 0; + } + } + + LinearGradient { + anchors { fill: mask } + source: mask + start: Qt.point(0, 0); + end: Qt.point(0, bar.height); + rotation: 180 + gradient: Gradient { + GradientStop { + position: 0.0; + color: colors.greenStart; + } + GradientStop { + position: 0.5; + color: colors.greenEnd; + } + GradientStop { + position: 1.0; + color: colors.red; + } + } + } + + Rectangle { + id: gatedIndicator; + visible: gated && !AudioScriptingInterface.clipping + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: "#0080FF"; + anchors { + right: parent.left; + verticalCenter: parent.verticalCenter; + } + } + + Rectangle { + id: clippingIndicator; + visible: AudioScriptingInterface.clipping + + radius: 4; + width: 2 * radius; + height: 2 * radius; + color: colors.red; + anchors { + left: parent.right; + verticalCenter: parent.verticalCenter; + } + } + } +} From 915d22bb15328ae7ddb3563b654c3a4ede6db043 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 10:38:26 -0800 Subject: [PATCH 03/78] adding bubble icon (not working) --- interface/resources/qml/AvatarInputsBar.qml | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index 4a071d2d04..b17dfdcd65 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -8,6 +8,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.4 +import QtGraphicalEffects 1.0 import "./hifi/audio" as HifiAudio @@ -21,10 +22,29 @@ Item { readonly property bool shouldReposition: true; - HifiAudio.MicBar { + HifiAudio.MicBarApplication { id: audio; visible: AvatarInputs.showAudioTools; standalone: true; - dragTarget: parent; + dragTarget: parent; + } + Image { + id: bubbleIcon + source: "../icons/tablet-icons/bubble-i.svg"; + width: 28; + height: 28; + anchors { + left: root.right + top: root.top + topMargin: (root.height - bubbleIcon.height) / 2 + } + } + ColorOverlay { + anchors.fill: bubbleIcon + source: bubbleIcon + color: Users.getIgnoreRadiusEnabled() ? Qt.rgba(31, 198, 166, 0.3) : Qt.rgba(255, 255, 255, 0.3); + onColorChanged: { + console.log("colorChanged") + } } } From 3ceb1598f614b18eb412c6962b459b79e8fcf376 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 14:54:13 -0800 Subject: [PATCH 04/78] adding working bubble icon --- interface/resources/qml/AvatarInputsBar.qml | 9 ++++----- interface/src/ui/AvatarInputs.cpp | 6 ++++++ interface/src/ui/AvatarInputs.h | 11 ++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index b17dfdcd65..6375211a93 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -16,10 +16,10 @@ Item { id: root; objectName: "AvatarInputsBar" property int modality: Qt.NonModal + readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled width: audio.width; height: audio.height; x: 10; y: 5; - readonly property bool shouldReposition: true; HifiAudio.MicBarApplication { @@ -40,11 +40,10 @@ Item { } } ColorOverlay { + id: bubbleIconOverlay anchors.fill: bubbleIcon source: bubbleIcon - color: Users.getIgnoreRadiusEnabled() ? Qt.rgba(31, 198, 166, 0.3) : Qt.rgba(255, 255, 255, 0.3); - onColorChanged: { - console.log("colorChanged") - } + color: AvatarInputs.ignoreRadiusEnabled ? "#1FC6A6" : "#FFFFFF"; + opacity: 0.7 } } diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 0aa352de23..6d43507a3e 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -30,6 +30,8 @@ AvatarInputs* AvatarInputs::getInstance() { AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); + auto nodeList = DependencyManager::get(); + connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged); } #define AI_UPDATE(name, src) \ @@ -83,6 +85,10 @@ void AvatarInputs::setShowAudioTools(bool showAudioTools) { emit showAudioToolsChanged(_showAudioTools); } +bool AvatarInputs::getIgnoreRadiusEnabled() const { + return DependencyManager::get()->getIgnoreRadiusEnabled(); +} + void AvatarInputs::toggleCameraMute() { FaceTracker* faceTracker = qApp->getSelectedFaceTracker(); if (faceTracker) { diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 6569792807..a41fd0485f 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -42,6 +42,7 @@ class AvatarInputs : public QObject { AI_PROPERTY(bool, isHMD, false) Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) + Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged) public: static AvatarInputs* getInstance(); @@ -55,7 +56,8 @@ public: AvatarInputs(QObject* parent = nullptr); void update(); - bool showAudioTools() const { return _showAudioTools; } + bool showAudioTools() const { return _showAudioTools; } + bool getIgnoreRadiusEnabled() const; public slots: @@ -93,6 +95,13 @@ signals: */ void showAudioToolsChanged(bool show); + /**jsdoc + * @function AvatarInputs.ignoreRadiusEnabledChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void ignoreRadiusEnabledChanged(bool enabled); + protected: /**jsdoc From ddacc0ee60fc7169eeac98e83725b28c2b6dffdd Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 14:56:21 -0800 Subject: [PATCH 05/78] bubble icon opacity to 0.3 --- interface/resources/qml/AvatarInputsBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index 6375211a93..d539d7ea9e 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -44,6 +44,6 @@ Item { anchors.fill: bubbleIcon source: bubbleIcon color: AvatarInputs.ignoreRadiusEnabled ? "#1FC6A6" : "#FFFFFF"; - opacity: 0.7 + opacity: 0.3 } } From e129201d2a04e2af0782d6f0b7b4e514805cbd0b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 17:38:11 -0800 Subject: [PATCH 06/78] adding more opacity, cleanup --- interface/resources/qml/AvatarInputsBar.qml | 69 +++++++++++++++---- .../qml/hifi/audio/MicBarApplication.qml | 10 +++ 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index d539d7ea9e..615e260833 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -7,11 +7,13 @@ // import Hifi 1.0 as Hifi -import QtQuick 2.4 +import QtQuick 2.5 import QtGraphicalEffects 1.0 import "./hifi/audio" as HifiAudio +import TabletScriptingInterface 1.0 + Item { id: root; objectName: "AvatarInputsBar" @@ -19,7 +21,8 @@ Item { readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled width: audio.width; height: audio.height; - x: 10; y: 5; + x: 10; + y: 5; readonly property bool shouldReposition: true; HifiAudio.MicBarApplication { @@ -28,22 +31,58 @@ Item { standalone: true; dragTarget: parent; } - Image { - id: bubbleIcon - source: "../icons/tablet-icons/bubble-i.svg"; - width: 28; - height: 28; + Rectangle { + id: bubbleRect + width: bubbleIcon.width + 10 + height: parent.height + radius: 5; + opacity: 0.5; + + color: "#00000000"; + border { + width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; + color: "#80FFFFFF"; + } anchors { left: root.right top: root.top - topMargin: (root.height - bubbleIcon.height) / 2 + } + + MouseArea { + id: mouseArea; + anchors.fill: parent + + hoverEnabled: true; + scrollGestureEnabled: false; + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + } + drag.target: root; + onContainsMouseChanged: { + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonHover); + bubbleRect.opacity = 0.7; + } else { + bubbleRect.opacity = 0.5; + } + } + } + Image { + id: bubbleIcon + source: "../icons/tablet-icons/bubble-i.svg"; + sourceSize: Qt.size(28, 28); + smooth: true; + visible: false + anchors.top: parent.top + anchors.topMargin: (parent.height - bubbleIcon.height) / 2 + anchors.left: parent.left + anchors.leftMargin: (parent.width - bubbleIcon.width) / 2 + } + ColorOverlay { + id: bubbleIconOverlay + anchors.fill: bubbleIcon + source: bubbleIcon + color: AvatarInputs.ignoreRadiusEnabled ? "#1FC6A6" : "#FFFFFF"; } } - ColorOverlay { - id: bubbleIconOverlay - anchors.fill: bubbleIcon - source: bubbleIcon - color: AvatarInputs.ignoreRadiusEnabled ? "#1FC6A6" : "#FFFFFF"; - opacity: 0.3 - } } diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 33824a3d86..ba6aeef7d8 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -32,6 +32,14 @@ Rectangle { radius: 5; + onLevelChanged: { + var rectOpacity = AudioScriptingInterface.muted && (level >= userSpeakingLevel)? 0.9 : 0.3; + if (mouseArea.containsMouse) { + rectOpacity = 0.5; + } + opacity = rectOpacity; + } + color: "#00000000"; border { width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; @@ -121,6 +129,7 @@ Rectangle { } ColorOverlay { + id: imageOverlay anchors { fill: image } source: image; color: colors.icon; @@ -170,6 +179,7 @@ Rectangle { height: 32; Rectangle { // base + id: baseBar radius: 4; anchors { fill: parent } color: colors.gutter; From 3be44e0a8ea30fc2500c2d3306e1487a4625e82f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 18:04:09 -0800 Subject: [PATCH 07/78] adding privacy shield files --- interface/src/ui/PrivacyShield.cpp | 12 ++++++++++++ interface/src/ui/PrivacyShield.h | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 interface/src/ui/PrivacyShield.cpp create mode 100644 interface/src/ui/PrivacyShield.h diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp new file mode 100644 index 0000000000..e1035ee5bb --- /dev/null +++ b/interface/src/ui/PrivacyShield.cpp @@ -0,0 +1,12 @@ +// +// PrivacyShield.h +// interface/src/ui +// +// Created by Wayne Chen on 2/27/19. +// 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 +// + +#include "PrivacyShield.cpp" diff --git a/interface/src/ui/PrivacyShield.h b/interface/src/ui/PrivacyShield.h new file mode 100644 index 0000000000..a609f2775b --- /dev/null +++ b/interface/src/ui/PrivacyShield.h @@ -0,0 +1,12 @@ +// +// PrivacyShield.h +// interface/src/ui +// +// Created by Wayne Chen on 2/27/19. +// 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 +// + +#pragma once From e1d03a23395d5f640b20f5544399305b34708bec Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 18:04:58 -0800 Subject: [PATCH 08/78] fixing typos --- interface/src/ui/PrivacyShield.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp index e1035ee5bb..12687afbea 100644 --- a/interface/src/ui/PrivacyShield.cpp +++ b/interface/src/ui/PrivacyShield.cpp @@ -1,5 +1,5 @@ // -// PrivacyShield.h +// PrivacyShield.cpp // interface/src/ui // // Created by Wayne Chen on 2/27/19. @@ -9,4 +9,4 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "PrivacyShield.cpp" +#include "PrivacyShield.h" From 835153c0dfb7caef7912715589bafea88313d7d4 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 15:50:13 -0800 Subject: [PATCH 09/78] don't display gated state --- .../qml/hifi/audio/MicBarApplication.qml | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index ba6aeef7d8..451f410e1d 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -17,6 +17,8 @@ import TabletScriptingInterface 1.0 Rectangle { readonly property var level: AudioScriptingInterface.inputLevel; + readonly property var clipping: AudioScriptingInterface.clipping; + readonly property var muted: AudioScriptingInterface.muted; readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { @@ -24,6 +26,9 @@ Rectangle { AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } + readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; + readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg"; property bool standalone: false; property var dragTarget: null; @@ -33,8 +38,8 @@ Rectangle { radius: 5; onLevelChanged: { - var rectOpacity = AudioScriptingInterface.muted && (level >= userSpeakingLevel)? 0.9 : 0.3; - if (mouseArea.containsMouse) { + var rectOpacity = muted && (level >= userSpeakingLevel) ? 0.9 : 0.3; + if (mouseArea.containsMouse && rectOpacity != 0.9) { rectOpacity = 0.5; } opacity = rectOpacity; @@ -87,8 +92,8 @@ Rectangle { QtObject { id: colors; - readonly property string unmuted: "#FFF"; - readonly property string muted: "#E2334D"; + readonly property string unmutedColor: "#FFF"; + readonly property string mutedColor: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; @@ -96,7 +101,7 @@ Rectangle { readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; + readonly property string icon: muted ? mutedColor : unmutedColor; } Item { @@ -112,14 +117,11 @@ Rectangle { Item { Image { - readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; - readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; - id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: muted ? mutedIcon : clipping ? clippingIcon : unmutedIcon; - width: 21; - height: 24; + width: 29; + height: 32; anchors { left: parent.left; leftMargin: 7; @@ -142,7 +144,8 @@ Rectangle { readonly property string color: colors.muted; - visible: AudioScriptingInterface.muted && (level >= userSpeakingLevel); + visible: muted && (level >= userSpeakingLevel); + opacity: 0.9 anchors { left: parent.left; @@ -215,14 +218,14 @@ Rectangle { } GradientStop { position: 1.0; - color: colors.red; + color: colors.yellow; } } } - +/* Rectangle { id: gatedIndicator; - visible: gated && !AudioScriptingInterface.clipping + visible: gated && !clipping radius: 4; width: 2 * radius; @@ -236,7 +239,7 @@ Rectangle { Rectangle { id: clippingIndicator; - visible: AudioScriptingInterface.clipping + visible: clipping radius: 4; width: 2 * radius; @@ -247,5 +250,6 @@ Rectangle { verticalCenter: parent.verticalCenter; } } +*/ } } From 26895e6f5d1a7dc37a9fa07b1d6be444c9ebfd80 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 15:53:24 -0800 Subject: [PATCH 10/78] don't display gated state --- interface/resources/icons/tablet-icons/mic-clip-i.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 interface/resources/icons/tablet-icons/mic-clip-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-clip-i.svg b/interface/resources/icons/tablet-icons/mic-clip-i.svg new file mode 100644 index 0000000000..d6f36427a9 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-clip-i.svg @@ -0,0 +1,3 @@ + + + From 087f613d60d242602737a2f1872c04badd70d1a3 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 16:48:17 -0800 Subject: [PATCH 11/78] fixing muted text --- .../icons/tablet-icons/mic-clip-i.svg | 9 ++- .../resources/icons/tablet-icons/mic-mute.svg | 60 ------------------- .../icons/tablet-icons/mic-unmute-i.svg | 23 +------ .../qml/hifi/audio/MicBarApplication.qml | 6 +- 4 files changed, 11 insertions(+), 87 deletions(-) delete mode 100644 interface/resources/icons/tablet-icons/mic-mute.svg diff --git a/interface/resources/icons/tablet-icons/mic-clip-i.svg b/interface/resources/icons/tablet-icons/mic-clip-i.svg index d6f36427a9..8bc1b2b558 100644 --- a/interface/resources/icons/tablet-icons/mic-clip-i.svg +++ b/interface/resources/icons/tablet-icons/mic-clip-i.svg @@ -1,3 +1,10 @@ - + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/mic-mute.svg b/interface/resources/icons/tablet-icons/mic-mute.svg deleted file mode 100644 index bd42fded05..0000000000 --- a/interface/resources/icons/tablet-icons/mic-mute.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-unmute-i.svg b/interface/resources/icons/tablet-icons/mic-unmute-i.svg index c4eda55cbc..f577a65bc2 100644 --- a/interface/resources/icons/tablet-icons/mic-unmute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-unmute-i.svg @@ -1,22 +1,3 @@ - - - - - - - - - - - - - + + diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 451f410e1d..2bf49aa83b 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -124,7 +124,6 @@ Rectangle { height: 32; anchors { left: parent.left; - leftMargin: 7; top: parent.top; topMargin: 5; } @@ -142,10 +141,7 @@ Rectangle { Item { id: status; - readonly property string color: colors.muted; - visible: muted && (level >= userSpeakingLevel); - opacity: 0.9 anchors { left: parent.left; @@ -162,7 +158,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: parent.color; + color: colors.mutedColor; text: "MUTED"; size: 12; From d382893e754cee57b27d1fe32b2d6c69e91b6dc2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 16:51:19 -0800 Subject: [PATCH 12/78] staging avatar inputs for ignore radius --- interface/src/Application.cpp | 10 ++ interface/src/avatar/AvatarManager.cpp | 2 + interface/src/ui/AvatarInputs.cpp | 3 + interface/src/ui/AvatarInputs.h | 25 +++ interface/src/ui/PrivacyShield.cpp | 147 ++++++++++++++++++ interface/src/ui/PrivacyShield.h | 35 +++++ libraries/avatars/src/AvatarData.h | 1 + .../UserActivityLoggerScriptingInterface.cpp | 8 +- .../UserActivityLoggerScriptingInterface.h | 4 +- scripts/system/bubble.js | 6 +- 10 files changed, 232 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 635932ea1c..e34ae4bcba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -211,6 +211,7 @@ #include "ui/UpdateDialog.h" #include "ui/DomainConnectionModel.h" #include "ui/Keyboard.h" +#include "ui/PrivacyShield.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -927,6 +928,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -2650,6 +2652,9 @@ void Application::cleanupBeforeQuit() { nodeList->getPacketReceiver().setShouldDropPackets(true); } + // destroy privacy shield before entity shutdown. + DependencyManager::get()->destroyPrivacyShield(); + getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) @@ -2728,6 +2733,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; @@ -5528,6 +5534,8 @@ void Application::resumeAfterLoginDialogActionTaken() { menu->getMenu("Developer")->setVisible(_developerMenuVisible); _myCamera.setMode(_previousCameraMode); cameraModeChanged(); + + DependencyManager::get()->createPrivacyShield(); } void Application::loadAvatarScripts(const QVector& urls) { @@ -6486,6 +6494,8 @@ void Application::update(float deltaTime) { updateLoginDialogPosition(); } + DependencyManager::get()->update(deltaTime); + { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 69f7054953..76612039db 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "Application.h" #include "InterfaceLogging.h" @@ -536,6 +537,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar avatar->removeAvatarEntitiesFromTree(); if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { + emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID()); emit DependencyManager::get()->enteredIgnoreRadius(); } else if (removalReason == KillAvatarReason::AvatarDisconnected) { // remove from node sets, if present diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 6d43507a3e..80604f354b 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "Application.h" #include "Menu.h" @@ -31,7 +32,9 @@ AvatarInputs* AvatarInputs::getInstance() { AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); auto nodeList = DependencyManager::get(); + auto usersScriptingInterface = DependencyManager::get(); connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged); + connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &AvatarInputs::enteredIgnoreRadiusChanged); } #define AI_UPDATE(name, src) \ diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index a41fd0485f..f53adc1749 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -43,6 +43,7 @@ class AvatarInputs : public QObject { Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged) + //Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged) public: static AvatarInputs* getInstance(); @@ -58,6 +59,7 @@ public: void update(); bool showAudioTools() const { return _showAudioTools; } bool getIgnoreRadiusEnabled() const; + //bool getEnteredIgnoreRadius() const; public slots: @@ -95,6 +97,20 @@ signals: */ void showAudioToolsChanged(bool show); + /**jsdoc + * @function AvatarInputs.avatarEnteredIgnoreRadius + * @param {QUuid} avatarID + * @returns {Signal} + */ + void avatarEnteredIgnoreRadius(QUuid avatarID); + + /**jsdoc + * @function AvatarInputs.avatarLeftIgnoreRadius + * @param {QUuid} avatarID + * @returns {Signal} + */ + void avatarLeftIgnoreRadius(QUuid avatarID); + /**jsdoc * @function AvatarInputs.ignoreRadiusEnabledChanged * @param {boolean} enabled @@ -102,6 +118,13 @@ signals: */ void ignoreRadiusEnabledChanged(bool enabled); + /**jsdoc + * @function AvatarInputs.enteredIgnoreRadiusChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void enteredIgnoreRadiusChanged(); + protected: /**jsdoc @@ -115,6 +138,8 @@ protected: Q_INVOKABLE void toggleCameraMute(); private: + void onAvatarEnteredIgnoreRadius(); + void onAvatarLeftIgnoreRadius(); float _trailingAudioLoudness{ 0 }; bool _showAudioTools { false }; }; diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp index 12687afbea..e8f61ff5bf 100644 --- a/interface/src/ui/PrivacyShield.cpp +++ b/interface/src/ui/PrivacyShield.cpp @@ -10,3 +10,150 @@ // #include "PrivacyShield.h" + +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "PathUtils.h" +#include "GLMHelpers.h" + +const int PRIVACY_SHIELD_VISIBLE_DURATION_MS = 3000; +const int PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS = 750; +const int PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS = 15000; +const float PRIVACY_SHIELD_HEIGHT_SCALE = 0.15f; + +PrivacyShield::PrivacyShield() { + auto usersScriptingInterface = DependencyManager::get(); + //connect(usersScriptingInterface.data(), &UsersScriptingInterface::ignoreRadiusEnabledChanged, [this](bool enabled) { + // onPrivacyShieldToggled(enabled); + //}); + //connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &PrivacyShield::enteredIgnoreRadius); +} + +void PrivacyShield::createPrivacyShield() { + // Affects bubble height + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarScale = myAvatar->getTargetScale(); + auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + auto avatarWorldPosition = myAvatar->getWorldPosition(); + auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + EntityItemProperties properties; + properties.setName("Privacy-Shield"); + properties.setModelURL(PathUtils::resourcesUrl("assets/models/Bubble-v14.fbx").toString()); + properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + properties.setPosition(glm::vec3(avatarWorldPosition.x, + -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); + properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + properties.setVisible(false); + + _localPrivacyShieldID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); + //_bubbleActivateSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl() + "assets/sounds/bubble.wav"); + + //onPrivacyShieldToggled(DependencyManager::get()->getIgnoreRadiusEnabled(), true); +} + +void PrivacyShield::destroyPrivacyShield() { + DependencyManager::get()->deleteEntity(_localPrivacyShieldID); +} + +void PrivacyShield::update(float deltaTime) { + if (_updateConnected) { + auto now = usecTimestampNow(); + auto delay = (now - _privacyShieldTimestamp); + auto privacyShieldAlpha = 1.0 - (delay / PRIVACY_SHIELD_VISIBLE_DURATION_MS); + if (privacyShieldAlpha > 0) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarScale = myAvatar->getTargetScale(); + auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + auto avatarWorldPosition = myAvatar->getWorldPosition(); + auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + EntityItemProperties properties; + properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); + if (delay < PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS) { + properties.setPosition(glm::vec3(avatarWorldPosition.x, + (-((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * avatarScale * 2.0 + + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setModelScale(glm::vec3(2.0, + ((1 - ((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * + (0.5 * (avatarScale + 1.0))), 2.0)); + } else { + properties.setPosition(glm::vec3(avatarWorldPosition.x, avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + } + DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); + } + else { + hidePrivacyShield(); + if (_updateConnected) { + _updateConnected = false; + } + } + } +} + +void PrivacyShield::enteredIgnoreRadius() { + showPrivacyShield(); + DependencyManager::get()->privacyShieldActivated(); +} + +void PrivacyShield::onPrivacyShieldToggled(bool enabled, bool doNotLog) { + if (!doNotLog) { + DependencyManager::get()->privacyShieldToggled(enabled); + } + if (enabled) { + showPrivacyShield(); + } else { + hidePrivacyShield(); + if (_updateConnected) { + _updateConnected = false; + } + } +} + +void PrivacyShield::showPrivacyShield() { + auto now = usecTimestampNow(); + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarScale = myAvatar->getTargetScale(); + auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + auto avatarWorldPosition = myAvatar->getWorldPosition(); + auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + if (now - _lastPrivacyShieldSoundTimestamp >= PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS) { + AudioInjectorOptions options; + options.position = avatarWorldPosition; + options.localOnly = true; + options.volume = 0.2f; + AudioInjector::playSoundAndDelete(_bubbleActivateSound, options); + _lastPrivacyShieldSoundTimestamp = now; + } + hidePrivacyShield(); + if (_updateConnected) { + _updateConnected = false; + } + + EntityItemProperties properties; + properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + properties.setPosition(glm::vec3(avatarWorldPosition.x, + -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + properties.setVisible(true); + + DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); + + _privacyShieldTimestamp = now; + _updateConnected = true; +} + +void PrivacyShield::hidePrivacyShield() { + EntityTreePointer entityTree = qApp->getEntities()->getTree(); + EntityItemPointer privacyShieldEntity = entityTree->findEntityByEntityItemID(EntityItemID(_localPrivacyShieldID)); + if (privacyShieldEntity) { + privacyShieldEntity->setVisible(false); + } +} diff --git a/interface/src/ui/PrivacyShield.h b/interface/src/ui/PrivacyShield.h index a609f2775b..5aecb661f7 100644 --- a/interface/src/ui/PrivacyShield.h +++ b/interface/src/ui/PrivacyShield.h @@ -10,3 +10,38 @@ // #pragma once + +#include +#include +#include + +#include +#include + +class PrivacyShield : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + PrivacyShield(); + void createPrivacyShield(); + void destroyPrivacyShield(); + + bool isVisible() const { return _visible; } + void update(float deltaTime); + +protected slots: + void enteredIgnoreRadius(); + void onPrivacyShieldToggled(bool enabled, bool doNotLog = false); + +private: + void showPrivacyShield(); + void hidePrivacyShield(); + + SharedSoundPointer _bubbleActivateSound; + QUuid _localPrivacyShieldID; + quint64 _privacyShieldTimestamp; + quint64 _lastPrivacyShieldSoundTimestamp; + bool _visible { false }; + bool _updateConnected { false }; +}; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 95bbcbeb16..32f53f77a3 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1477,6 +1477,7 @@ protected: glm::vec3 _globalBoundingBoxOffset; AABox _defaultBubbleBox; + AABox _fitBoundingBox; mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index c63170de75..2f47ef5e00 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -71,12 +71,12 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b doLogAction("makeUserConnection", payload); } -void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) { - doLogAction(newValue ? "bubbleOn" : "bubbleOff"); +void UserActivityLoggerScriptingInterface::privacyShieldToggled(bool newValue) { + doLogAction(newValue ? "privacyShieldOn" : "privacyShieldOff"); } -void UserActivityLoggerScriptingInterface::bubbleActivated() { - doLogAction("bubbleActivated"); +void UserActivityLoggerScriptingInterface::privacyShieldActivated() { + doLogAction("privacyShieldActivated"); } void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) { diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index 71d411056d..1cda1235e9 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -30,8 +30,8 @@ public: Q_INVOKABLE void palAction(QString action, QString target); Q_INVOKABLE void palOpened(float secondsOpen); Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = ""); - Q_INVOKABLE void bubbleToggled(bool newValue); - Q_INVOKABLE void bubbleActivated(); + Q_INVOKABLE void privacyShieldToggled(bool newValue); + Q_INVOKABLE void privacyShieldActivated(); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem); Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, QString contentCreator, int cost, bool firstPurchaseOfThisItem, QString errorDetails); diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 6ca624872e..eca3b3dcd4 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -90,7 +90,7 @@ // Called from the C++ scripting interface to show the bubble overlay function enteredIgnoreRadius() { createOverlays(); - UserActivityLogger.bubbleActivated(); + UserActivityLogger.privacyShieldActivated(); } // Used to set the state of the bubble HUD button @@ -160,7 +160,7 @@ function onBubbleToggled(enabled, doNotLog) { writeButtonProperties(enabled); if (doNotLog !== true) { - UserActivityLogger.bubbleToggled(enabled); + UserActivityLogger.privacyShieldToggled(enabled); } if (enabled) { createOverlays(); @@ -174,7 +174,7 @@ } // Setup the bubble button - var buttonName = "BUBBLE"; + var buttonName = "SHIELD"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/bubble-i.svg", From 811fa8dcb29a3cb306fef95e49d98a5f824bd350 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 1 Mar 2019 09:12:03 -0800 Subject: [PATCH 13/78] allowing bubble icon to toggle --- interface/resources/qml/AvatarInputsBar.qml | 8 +- interface/src/ui/PrivacyShield.cpp | 96 ++++++++++----------- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index 615e260833..ead4fbc618 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -36,7 +36,7 @@ Item { width: bubbleIcon.width + 10 height: parent.height radius: 5; - opacity: 0.5; + opacity: AvatarInputs.ignoreRadiusEnabled ? 0.7 : 0.3; color: "#00000000"; border { @@ -56,14 +56,12 @@ Item { scrollGestureEnabled: false; onClicked: { Tablet.playSound(TabletEnums.ButtonClick); + Users.toggleIgnoreRadius(); } drag.target: root; onContainsMouseChanged: { if (containsMouse) { Tablet.playSound(TabletEnums.ButtonHover); - bubbleRect.opacity = 0.7; - } else { - bubbleRect.opacity = 0.5; } } } @@ -82,7 +80,7 @@ Item { id: bubbleIconOverlay anchors.fill: bubbleIcon source: bubbleIcon - color: AvatarInputs.ignoreRadiusEnabled ? "#1FC6A6" : "#FFFFFF"; + color: "#FFFFFF"; } } } diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp index e8f61ff5bf..279c05ee2d 100644 --- a/interface/src/ui/PrivacyShield.cpp +++ b/interface/src/ui/PrivacyShield.cpp @@ -37,22 +37,22 @@ PrivacyShield::PrivacyShield() { void PrivacyShield::createPrivacyShield() { // Affects bubble height - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarScale = myAvatar->getTargetScale(); - auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - auto avatarWorldPosition = myAvatar->getWorldPosition(); - auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - EntityItemProperties properties; - properties.setName("Privacy-Shield"); - properties.setModelURL(PathUtils::resourcesUrl("assets/models/Bubble-v14.fbx").toString()); - properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - properties.setPosition(glm::vec3(avatarWorldPosition.x, - -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); - properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - properties.setVisible(false); + //auto myAvatar = DependencyManager::get()->getMyAvatar(); + //auto avatarScale = myAvatar->getTargetScale(); + //auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + //auto avatarWorldPosition = myAvatar->getWorldPosition(); + //auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + //EntityItemProperties properties; + //properties.setName("Privacy-Shield"); + //properties.setModelURL(PathUtils::resourcesUrl("assets/models/Bubble-v14.fbx").toString()); + //properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + //properties.setPosition(glm::vec3(avatarWorldPosition.x, + // -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + //properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); + //properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + //properties.setVisible(false); - _localPrivacyShieldID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); + //_localPrivacyShieldID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); //_bubbleActivateSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl() + "assets/sounds/bubble.wav"); //onPrivacyShieldToggled(DependencyManager::get()->getIgnoreRadiusEnabled(), true); @@ -63,39 +63,39 @@ void PrivacyShield::destroyPrivacyShield() { } void PrivacyShield::update(float deltaTime) { - if (_updateConnected) { - auto now = usecTimestampNow(); - auto delay = (now - _privacyShieldTimestamp); - auto privacyShieldAlpha = 1.0 - (delay / PRIVACY_SHIELD_VISIBLE_DURATION_MS); - if (privacyShieldAlpha > 0) { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarScale = myAvatar->getTargetScale(); - auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - auto avatarWorldPosition = myAvatar->getWorldPosition(); - auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - EntityItemProperties properties; - properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); - if (delay < PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS) { - properties.setPosition(glm::vec3(avatarWorldPosition.x, - (-((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * avatarScale * 2.0 + - avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setModelScale(glm::vec3(2.0, - ((1 - ((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * - (0.5 * (avatarScale + 1.0))), 2.0)); - } else { - properties.setPosition(glm::vec3(avatarWorldPosition.x, avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - } - DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); - } - else { - hidePrivacyShield(); - if (_updateConnected) { - _updateConnected = false; - } - } - } + //if (_updateConnected) { + // auto now = usecTimestampNow(); + // auto delay = (now - _privacyShieldTimestamp); + // auto privacyShieldAlpha = 1.0 - (delay / PRIVACY_SHIELD_VISIBLE_DURATION_MS); + // if (privacyShieldAlpha > 0) { + // auto myAvatar = DependencyManager::get()->getMyAvatar(); + // auto avatarScale = myAvatar->getTargetScale(); + // auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + // auto avatarWorldPosition = myAvatar->getWorldPosition(); + // auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + // EntityItemProperties properties; + // properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + // properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); + // if (delay < PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS) { + // properties.setPosition(glm::vec3(avatarWorldPosition.x, + // (-((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * avatarScale * 2.0 + + // avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + // properties.setModelScale(glm::vec3(2.0, + // ((1 - ((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * + // (0.5 * (avatarScale + 1.0))), 2.0)); + // } else { + // properties.setPosition(glm::vec3(avatarWorldPosition.x, avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + // properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + // } + // DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); + // } + // else { + // hidePrivacyShield(); + // if (_updateConnected) { + // _updateConnected = false; + // } + // } + //} } void PrivacyShield::enteredIgnoreRadius() { From 5b6ce8736d3a46c0f12a40aff572f048e8b7c971 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 1 Mar 2019 11:28:00 -0800 Subject: [PATCH 14/78] adding new icons with gated icon --- .../icons/tablet-icons/mic-clip-i.svg | 5 ++- .../icons/tablet-icons/mic-gate-i.svg | 6 ++++ .../icons/tablet-icons/mic-mute-i.svg | 32 +++++-------------- .../icons/tablet-icons/mic-unmute-i.svg | 6 +++- .../qml/hifi/audio/MicBarApplication.qml | 3 +- 5 files changed, 25 insertions(+), 27 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-gate-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-clip-i.svg b/interface/resources/icons/tablet-icons/mic-clip-i.svg index 8bc1b2b558..c1989db2d6 100644 --- a/interface/resources/icons/tablet-icons/mic-clip-i.svg +++ b/interface/resources/icons/tablet-icons/mic-clip-i.svg @@ -1,6 +1,9 @@ - + + + + diff --git a/interface/resources/icons/tablet-icons/mic-gate-i.svg b/interface/resources/icons/tablet-icons/mic-gate-i.svg new file mode 100644 index 0000000000..a5a5a621d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-gate-i.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/interface/resources/icons/tablet-icons/mic-mute-i.svg b/interface/resources/icons/tablet-icons/mic-mute-i.svg index 9dc2c53443..0da394a5a2 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-i.svg @@ -1,25 +1,9 @@ - - - - - - - - - - - - - - - + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/mic-unmute-i.svg b/interface/resources/icons/tablet-icons/mic-unmute-i.svg index f577a65bc2..ebfcc21e37 100644 --- a/interface/resources/icons/tablet-icons/mic-unmute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-unmute-i.svg @@ -1,3 +1,7 @@ - + + + + + diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 2bf49aa83b..30cef44bce 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -29,6 +29,7 @@ Rectangle { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg"; + readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg"; property bool standalone: false; property var dragTarget: null; @@ -118,7 +119,7 @@ Rectangle { Item { Image { id: image; - source: muted ? mutedIcon : clipping ? clippingIcon : unmutedIcon; + source: muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; width: 29; height: 32; From 4dca4cbc40f421d63836a45b8bbe4d214fcffc45 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 1 Mar 2019 11:38:32 -0800 Subject: [PATCH 15/78] update icons again --- .../icons/tablet-icons/mic-clip-i.svg | 5 +- .../icons/tablet-icons/mic-gate-i.svg | 5 +- .../icons/tablet-icons/mic-mute-a.svg | 26 +------ .../icons/tablet-icons/mic-mute-i.svg | 8 +- .../icons/tablet-icons/mic-unmute-a.svg | 73 +------------------ .../icons/tablet-icons/mic-unmute-i.svg | 6 +- 6 files changed, 9 insertions(+), 114 deletions(-) diff --git a/interface/resources/icons/tablet-icons/mic-clip-i.svg b/interface/resources/icons/tablet-icons/mic-clip-i.svg index c1989db2d6..f912c1e744 100644 --- a/interface/resources/icons/tablet-icons/mic-clip-i.svg +++ b/interface/resources/icons/tablet-icons/mic-clip-i.svg @@ -1,9 +1,6 @@ - - - - + diff --git a/interface/resources/icons/tablet-icons/mic-gate-i.svg b/interface/resources/icons/tablet-icons/mic-gate-i.svg index a5a5a621d7..8255174532 100644 --- a/interface/resources/icons/tablet-icons/mic-gate-i.svg +++ b/interface/resources/icons/tablet-icons/mic-gate-i.svg @@ -1,6 +1,3 @@ - - - - + diff --git a/interface/resources/icons/tablet-icons/mic-mute-a.svg b/interface/resources/icons/tablet-icons/mic-mute-a.svg index 9dc2c53443..67eafc27c8 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-a.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-a.svg @@ -1,25 +1,3 @@ - - - - - - - - - - - - - - - + + diff --git a/interface/resources/icons/tablet-icons/mic-mute-i.svg b/interface/resources/icons/tablet-icons/mic-mute-i.svg index 0da394a5a2..63af1b0da8 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-i.svg @@ -1,9 +1,3 @@ - - - - - - - + diff --git a/interface/resources/icons/tablet-icons/mic-unmute-a.svg b/interface/resources/icons/tablet-icons/mic-unmute-a.svg index b1464f207d..0bf7677017 100644 --- a/interface/resources/icons/tablet-icons/mic-unmute-a.svg +++ b/interface/resources/icons/tablet-icons/mic-unmute-a.svg @@ -1,70 +1,3 @@ - - - -image/svg+xml \ No newline at end of file + + + diff --git a/interface/resources/icons/tablet-icons/mic-unmute-i.svg b/interface/resources/icons/tablet-icons/mic-unmute-i.svg index ebfcc21e37..0bf7677017 100644 --- a/interface/resources/icons/tablet-icons/mic-unmute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-unmute-i.svg @@ -1,7 +1,3 @@ - - - - - + From 77b7cc2457cf9f9412116d51e59edc7c9c2461cb Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 14 Mar 2019 10:28:18 -0700 Subject: [PATCH 16/78] separating out bubble icon qml - adding support for transparency --- interface/resources/qml/AvatarInputsBar.qml | 53 +----- interface/resources/qml/BubbleIcon.qml | 82 +++++++++ interface/resources/qml/hifi/audio/MicBar.qml | 34 ++-- .../qml/hifi/audio/MicBarApplication.qml | 80 ++++----- interface/src/Application.cpp | 65 +++++-- interface/src/Application.h | 7 +- interface/src/scripting/Audio.cpp | 8 +- interface/src/ui/PrivacyShield.cpp | 159 ------------------ interface/src/ui/PrivacyShield.h | 47 ------ scripts/defaultScripts.js | 3 +- 10 files changed, 204 insertions(+), 334 deletions(-) create mode 100644 interface/resources/qml/BubbleIcon.qml delete mode 100644 interface/src/ui/PrivacyShield.cpp delete mode 100644 interface/src/ui/PrivacyShield.h diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index ead4fbc618..dfff103aa0 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -31,56 +31,7 @@ Item { standalone: true; dragTarget: parent; } - Rectangle { - id: bubbleRect - width: bubbleIcon.width + 10 - height: parent.height - radius: 5; - opacity: AvatarInputs.ignoreRadiusEnabled ? 0.7 : 0.3; - - color: "#00000000"; - border { - width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; - color: "#80FFFFFF"; - } - anchors { - left: root.right - top: root.top - } - - MouseArea { - id: mouseArea; - anchors.fill: parent - - hoverEnabled: true; - scrollGestureEnabled: false; - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - Users.toggleIgnoreRadius(); - } - drag.target: root; - onContainsMouseChanged: { - if (containsMouse) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - } - Image { - id: bubbleIcon - source: "../icons/tablet-icons/bubble-i.svg"; - sourceSize: Qt.size(28, 28); - smooth: true; - visible: false - anchors.top: parent.top - anchors.topMargin: (parent.height - bubbleIcon.height) / 2 - anchors.left: parent.left - anchors.leftMargin: (parent.width - bubbleIcon.width) / 2 - } - ColorOverlay { - id: bubbleIconOverlay - anchors.fill: bubbleIcon - source: bubbleIcon - color: "#FFFFFF"; - } + BubbleIcon { + dragTarget: parent } } diff --git a/interface/resources/qml/BubbleIcon.qml b/interface/resources/qml/BubbleIcon.qml new file mode 100644 index 0000000000..f9c57697f0 --- /dev/null +++ b/interface/resources/qml/BubbleIcon.qml @@ -0,0 +1,82 @@ +// +// Created by Bradley Austin Davis on 2015/06/19 +// 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 Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "./hifi/audio" as HifiAudio + +import TabletScriptingInterface 1.0 + +Rectangle { + id: bubbleRect + width: bubbleIcon.width + 10 + height: bubbleIcon.height + 10 + radius: 5; + opacity: AvatarInputs.ignoreRadiusEnabled ? 0.7 : 0.3; + property var dragTarget: null; + + color: "#00000000"; + border { + width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; + color: "#80FFFFFF"; + } + anchors { + left: dragTarget ? dragTarget.right : undefined + top: dragTarget ? dragTarget.top : undefined + } + + // borders are painted over fill, so reduce the fill to fit inside the border + Rectangle { + color: "#55000000"; + width: 40; + height: 40; + + radius: 5; + + anchors { + verticalCenter: parent.verticalCenter; + horizontalCenter: parent.horizontalCenter; + } + } + + MouseArea { + id: mouseArea; + anchors.fill: parent + + hoverEnabled: true; + scrollGestureEnabled: false; + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + Users.toggleIgnoreRadius(); + } + drag.target: dragTarget; + onContainsMouseChanged: { + if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + } + Image { + id: bubbleIcon + source: "../icons/tablet-icons/bubble-i.svg"; + sourceSize: Qt.size(28, 28); + smooth: true; + anchors.top: parent.top + anchors.topMargin: (parent.height - bubbleIcon.height) / 2 + anchors.left: parent.left + anchors.leftMargin: (parent.width - bubbleIcon.width) / 2 + } + ColorOverlay { + id: bubbleIconOverlay + anchors.fill: bubbleIcon + source: bubbleIcon + color: "#FFFFFF"; + } +} diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 7ed9451a04..fba06ac987 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -19,13 +19,18 @@ Rectangle { HifiConstants { id: hifi; } readonly property var level: AudioScriptingInterface.inputLevel; - + readonly property var clipping: AudioScriptingInterface.clipping; + readonly property var muted: AudioScriptingInterface.muted; + readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; + readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; + + readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } - + property bool standalone: false; property var dragTarget: null; @@ -70,7 +75,7 @@ Rectangle { if (AudioScriptingInterface.pushToTalk) { return; } - AudioScriptingInterface.muted = !AudioScriptingInterface.muted; + muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; @@ -113,9 +118,12 @@ Rectangle { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; + readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg"; + readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg"; id: image; - source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : + clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; width: 30; height: 30; @@ -138,9 +146,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: muted ? colors.muted : colors.unmuted; - visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || (AudioScriptingInterface.muted && (level >= userSpeakingLevel)); + visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; @@ -159,7 +167,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -235,12 +243,12 @@ Rectangle { } } } - + Rectangle { id: gatedIndicator; visible: gated && !AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: "#0080FF"; @@ -249,12 +257,12 @@ Rectangle { verticalCenter: parent.verticalCenter; } } - + Rectangle { id: clippingIndicator; visible: AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: colors.red; diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 30cef44bce..bfac278ee4 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -1,5 +1,5 @@ // -// MicBar.qml +// MicBarApplication.qml // qml/hifi/audio // // Created by Zach Pomerantz on 6/14/2017 @@ -16,9 +16,12 @@ import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + id: micBar; readonly property var level: AudioScriptingInterface.inputLevel; readonly property var clipping: AudioScriptingInterface.clipping; readonly property var muted: AudioScriptingInterface.muted; + readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; + readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { @@ -28,6 +31,7 @@ Rectangle { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; readonly property string clippingIcon: "../../../icons/tablet-icons/mic-clip-i.svg"; readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg"; property bool standalone: false; @@ -37,13 +41,16 @@ Rectangle { height: 44; radius: 5; + opacity: 0.7 onLevelChanged: { var rectOpacity = muted && (level >= userSpeakingLevel) ? 0.9 : 0.3; - if (mouseArea.containsMouse && rectOpacity != 0.9) { + if (pushToTalk && !pushingToTalk) { + rectOpacity = (level >= userSpeakingLevel) ? 0.9 : 0.7; + } else if (mouseArea.containsMouse && rectOpacity != 0.9) { rectOpacity = 0.5; } - opacity = rectOpacity; + micBar.opacity = rectOpacity; } color: "#00000000"; @@ -94,15 +101,15 @@ Rectangle { id: colors; readonly property string unmutedColor: "#FFF"; + readonly property string gatedColor: "#00BDFF"; readonly property string mutedColor: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; readonly property string yellow: "#C0C000"; - readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: muted ? mutedColor : unmutedColor; + readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor; } Item { @@ -110,7 +117,7 @@ Rectangle { anchors { left: parent.left; - verticalCenter: parent.verticalCenter; + top: parent.top; } width: 40; @@ -119,8 +126,8 @@ Rectangle { Item { Image { id: image; - source: muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; - + source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : + clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; width: 29; height: 32; anchors { @@ -134,7 +141,8 @@ Rectangle { id: imageOverlay anchors { fill: image } source: image; - color: colors.icon; + color: (pushToTalk && !pushingToTalk) ? ((level >= userSpeakingLevel) ? colors.mutedColor : + colors.unmutedColor) : colors.icon; } } } @@ -142,26 +150,34 @@ Rectangle { Item { id: status; - visible: muted && (level >= userSpeakingLevel); + visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - top: parent.bottom - topMargin: 5 + top: icon.bottom; + topMargin: 5; } - width: icon.width; - height: 8 + width: parent.width; + height: statusTextMetrics.height; + + TextMetrics { + id: statusTextMetrics + text: statusText.text + font: statusText.font + } RalewaySemiBold { + id: statusText anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; } - color: colors.mutedColor; + color: (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor; + font.bold: true - text: "MUTED"; + text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE"); size: 12; } } @@ -172,7 +188,8 @@ Rectangle { anchors { right: parent.right; rightMargin: 7; - verticalCenter: parent.verticalCenter; + top: parent.top + topMargin: 5 } width: 8; @@ -219,34 +236,5 @@ Rectangle { } } } -/* - Rectangle { - id: gatedIndicator; - visible: gated && !clipping - - radius: 4; - width: 2 * radius; - height: 2 * radius; - color: "#0080FF"; - anchors { - right: parent.left; - verticalCenter: parent.verticalCenter; - } - } - - Rectangle { - id: clippingIndicator; - visible: clipping - - radius: 4; - width: 2 * radius; - height: 2 * radius; - color: colors.red; - anchors { - left: parent.right; - verticalCenter: parent.verticalCenter; - } - } -*/ } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e34ae4bcba..9fd0781eef 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -211,7 +211,6 @@ #include "ui/UpdateDialog.h" #include "ui/DomainConnectionModel.h" #include "ui/Keyboard.h" -#include "ui/PrivacyShield.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -339,6 +338,10 @@ Setting::Handle maxOctreePacketsPerSecond{"maxOctreePPS", DEFAULT_MAX_OCTRE Setting::Handle loginDialogPoppedUp{"loginDialogPoppedUp", false}; +static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml"); +static const QUrl MIC_BAR_ENTITY_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml"); +static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml"); + static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action"; static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)"; static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json"; @@ -928,7 +931,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); return previousSessionCrashed; } @@ -1293,6 +1295,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setCrashAnnotation("display_plugin", displayPlugin->getName().toStdString()); setCrashAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0"); }); + connect(this, &Application::activeDisplayPluginChanged, this, [&](){ +#if !defined(Q_OS_ANDROID) + if (!getLoginDialogPoppedUp() && _desktopRootItemCreated) { + if (isHMDMode()) { + createAvatarInputsBar(); + auto offscreenUi = getOffscreenUI(); + offscreenUi->hide(AVATAR_INPUTS_BAR_QML.toString()); + } else { + destroyAvatarInputsBar(); + auto offscreenUi = getOffscreenUI(); + offscreenUi->show(AVATAR_INPUTS_BAR_QML.toString(), "AvatarInputsBar"); + } + } +#endif + }); connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode); connect(this, &Application::activeDisplayPluginChanged, this, [&](){ if (getLoginDialogPoppedUp()) { @@ -2378,7 +2395,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); }); auto rootItemLoadedFunctor = [webSurface, url, isTablet] { - Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString()); + Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString() || url == AVATAR_INPUTS_BAR_QML.toString() || + url == BUBBLE_ICON_QML.toString() || url == MIC_BAR_ENTITY_QML.toString() ); }; if (webSurface->getRootItem()) { rootItemLoadedFunctor(); @@ -2652,9 +2670,6 @@ void Application::cleanupBeforeQuit() { nodeList->getPacketReceiver().setShouldDropPackets(true); } - // destroy privacy shield before entity shutdown. - DependencyManager::get()->destroyPrivacyShield(); - getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) @@ -2733,7 +2748,6 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - DependencyManager::destroy(); DependencyManager::destroy(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; @@ -3301,6 +3315,7 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml"); offscreenUi->show(qml, "AvatarInputsBar"); #endif + _desktopRootItemCreated = true; } void Application::userKickConfirmation(const QUuid& nodeID) { @@ -5534,8 +5549,6 @@ void Application::resumeAfterLoginDialogActionTaken() { menu->getMenu("Developer")->setVisible(_developerMenuVisible); _myCamera.setMode(_previousCameraMode); cameraModeChanged(); - - DependencyManager::get()->createPrivacyShield(); } void Application::loadAvatarScripts(const QVector& urls) { @@ -6494,8 +6507,6 @@ void Application::update(float deltaTime) { updateLoginDialogPosition(); } - DependencyManager::get()->update(deltaTime); - { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); @@ -8986,6 +8997,38 @@ void Application::updateLoginDialogPosition() { } } +void Application::createAvatarInputsBar() { + const glm::vec3 LOCAL_POSITION { 0.0, 0.0, -1.0 }; + // DEFAULT_DPI / tablet scale percentage + const float DPI = 31.0f / (75.0f / 100.0f); + + EntityItemProperties properties; + properties.setType(EntityTypes::Web); + properties.setName("AvatarInputsBarEntity"); + properties.setSourceUrl(AVATAR_INPUTS_BAR_QML.toString()); + properties.setParentID(getMyAvatar()->getSelfID()); + properties.setParentJointIndex(getMyAvatar()->getJointIndex("_CAMERA_MATRIX")); + properties.setPosition(LOCAL_POSITION); + properties.setLocalRotation(Quaternions::IDENTITY); + //properties.setDimensions(LOGIN_DIMENSIONS); + properties.setPrimitiveMode(PrimitiveMode::SOLID); + properties.getGrab().setGrabbable(false); + properties.setIgnorePickIntersection(false); + properties.setAlpha(1.0f); + properties.setDPI(DPI); + properties.setVisible(true); + + auto entityScriptingInterface = DependencyManager::get(); + _avatarInputsBarID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); +} + +void Application::destroyAvatarInputsBar() { + auto entityScriptingInterface = DependencyManager::get(); + if (!_avatarInputsBarID.isNull()) { + entityScriptingInterface->deleteEntity(_avatarInputsBarID); + } +} + bool Application::hasRiftControllers() { return PluginUtils::isOculusTouchControllerAvailable(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 762ac9585a..99e57f1866 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -330,6 +330,9 @@ public: void createLoginDialog(); void updateLoginDialogPosition(); + void createAvatarInputsBar(); + void destroyAvatarInputsBar(); + // Check if a headset is connected bool hasRiftControllers(); bool hasViveControllers(); @@ -704,12 +707,14 @@ private: int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS; bool _interstitialModeEnabled{ false }; - bool _loginDialogPoppedUp = false; + bool _loginDialogPoppedUp{ false }; + bool _desktopRootItemCreated{ false }; bool _developerMenuVisible{ false }; QString _previousAvatarSkeletonModel; float _previousAvatarTargetScale; CameraMode _previousCameraMode; QUuid _loginDialogID; + QUuid _avatarInputsBarID; LoginStateManager _loginStateManager; quint64 _lastFaceTrackerUpdate; diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index bf43db3044..434688e474 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -224,10 +224,10 @@ void Audio::saveData() { } void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); + setMutedDesktop(_desktopMutedSetting.get()); + setMutedHMD(_hmdMutedSetting.get()); + setPTTDesktop(_pttDesktopSetting.get()); + setPTTHMD(_pttHMDSetting.get()); } bool Audio::getPTTHMD() const { diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp deleted file mode 100644 index 279c05ee2d..0000000000 --- a/interface/src/ui/PrivacyShield.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// -// PrivacyShield.cpp -// interface/src/ui -// -// Created by Wayne Chen on 2/27/19. -// 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 -// - -#include "PrivacyShield.h" - -#include -#include -#include -#include -#include -#include - -#include "Application.h" -#include "PathUtils.h" -#include "GLMHelpers.h" - -const int PRIVACY_SHIELD_VISIBLE_DURATION_MS = 3000; -const int PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS = 750; -const int PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS = 15000; -const float PRIVACY_SHIELD_HEIGHT_SCALE = 0.15f; - -PrivacyShield::PrivacyShield() { - auto usersScriptingInterface = DependencyManager::get(); - //connect(usersScriptingInterface.data(), &UsersScriptingInterface::ignoreRadiusEnabledChanged, [this](bool enabled) { - // onPrivacyShieldToggled(enabled); - //}); - //connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &PrivacyShield::enteredIgnoreRadius); -} - -void PrivacyShield::createPrivacyShield() { - // Affects bubble height - //auto myAvatar = DependencyManager::get()->getMyAvatar(); - //auto avatarScale = myAvatar->getTargetScale(); - //auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - //auto avatarWorldPosition = myAvatar->getWorldPosition(); - //auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - //EntityItemProperties properties; - //properties.setName("Privacy-Shield"); - //properties.setModelURL(PathUtils::resourcesUrl("assets/models/Bubble-v14.fbx").toString()); - //properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - //properties.setPosition(glm::vec3(avatarWorldPosition.x, - // -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - //properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); - //properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - //properties.setVisible(false); - - //_localPrivacyShieldID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); - //_bubbleActivateSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl() + "assets/sounds/bubble.wav"); - - //onPrivacyShieldToggled(DependencyManager::get()->getIgnoreRadiusEnabled(), true); -} - -void PrivacyShield::destroyPrivacyShield() { - DependencyManager::get()->deleteEntity(_localPrivacyShieldID); -} - -void PrivacyShield::update(float deltaTime) { - //if (_updateConnected) { - // auto now = usecTimestampNow(); - // auto delay = (now - _privacyShieldTimestamp); - // auto privacyShieldAlpha = 1.0 - (delay / PRIVACY_SHIELD_VISIBLE_DURATION_MS); - // if (privacyShieldAlpha > 0) { - // auto myAvatar = DependencyManager::get()->getMyAvatar(); - // auto avatarScale = myAvatar->getTargetScale(); - // auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - // auto avatarWorldPosition = myAvatar->getWorldPosition(); - // auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - // EntityItemProperties properties; - // properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - // properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); - // if (delay < PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS) { - // properties.setPosition(glm::vec3(avatarWorldPosition.x, - // (-((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * avatarScale * 2.0 + - // avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - // properties.setModelScale(glm::vec3(2.0, - // ((1 - ((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * - // (0.5 * (avatarScale + 1.0))), 2.0)); - // } else { - // properties.setPosition(glm::vec3(avatarWorldPosition.x, avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - // properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - // } - // DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); - // } - // else { - // hidePrivacyShield(); - // if (_updateConnected) { - // _updateConnected = false; - // } - // } - //} -} - -void PrivacyShield::enteredIgnoreRadius() { - showPrivacyShield(); - DependencyManager::get()->privacyShieldActivated(); -} - -void PrivacyShield::onPrivacyShieldToggled(bool enabled, bool doNotLog) { - if (!doNotLog) { - DependencyManager::get()->privacyShieldToggled(enabled); - } - if (enabled) { - showPrivacyShield(); - } else { - hidePrivacyShield(); - if (_updateConnected) { - _updateConnected = false; - } - } -} - -void PrivacyShield::showPrivacyShield() { - auto now = usecTimestampNow(); - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarScale = myAvatar->getTargetScale(); - auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - auto avatarWorldPosition = myAvatar->getWorldPosition(); - auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - if (now - _lastPrivacyShieldSoundTimestamp >= PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS) { - AudioInjectorOptions options; - options.position = avatarWorldPosition; - options.localOnly = true; - options.volume = 0.2f; - AudioInjector::playSoundAndDelete(_bubbleActivateSound, options); - _lastPrivacyShieldSoundTimestamp = now; - } - hidePrivacyShield(); - if (_updateConnected) { - _updateConnected = false; - } - - EntityItemProperties properties; - properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - properties.setPosition(glm::vec3(avatarWorldPosition.x, - -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - properties.setVisible(true); - - DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); - - _privacyShieldTimestamp = now; - _updateConnected = true; -} - -void PrivacyShield::hidePrivacyShield() { - EntityTreePointer entityTree = qApp->getEntities()->getTree(); - EntityItemPointer privacyShieldEntity = entityTree->findEntityByEntityItemID(EntityItemID(_localPrivacyShieldID)); - if (privacyShieldEntity) { - privacyShieldEntity->setVisible(false); - } -} diff --git a/interface/src/ui/PrivacyShield.h b/interface/src/ui/PrivacyShield.h deleted file mode 100644 index 5aecb661f7..0000000000 --- a/interface/src/ui/PrivacyShield.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// PrivacyShield.h -// interface/src/ui -// -// Created by Wayne Chen on 2/27/19. -// 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 -// - -#pragma once - -#include -#include -#include - -#include -#include - -class PrivacyShield : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - PrivacyShield(); - void createPrivacyShield(); - void destroyPrivacyShield(); - - bool isVisible() const { return _visible; } - void update(float deltaTime); - -protected slots: - void enteredIgnoreRadius(); - void onPrivacyShieldToggled(bool enabled, bool doNotLog = false); - -private: - void showPrivacyShield(); - void hidePrivacyShield(); - - SharedSoundPointer _bubbleActivateSound; - QUuid _localPrivacyShieldID; - quint64 _privacyShieldTimestamp; - quint64 _lastPrivacyShieldSoundTimestamp; - bool _visible { false }; - bool _updateConnected { false }; -}; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index e392680df9..bd7e79dffc 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,8 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js", - "system/audioMuteOverlay.js" + "system/miniTablet.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", From d834c5aca1c7f7d98ff895f0e7356d50a876d998 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 14 Mar 2019 15:05:26 -0700 Subject: [PATCH 17/78] adding test script for creating avatar input bar --- scripts/defaultScripts.js | 3 +- scripts/system/createAvatarInputsBarEntity.js | 76 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 scripts/system/createAvatarInputsBarEntity.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index bd7e79dffc..0d9799a035 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js" + "system/miniTablet.js", + "system/createAvatarInputsBarEntity.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js new file mode 100644 index 0000000000..babf519035 --- /dev/null +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -0,0 +1,76 @@ +"use strict"; + +(function(){ + + var button; + var buttonName = "AVBAR"; + var onCreateAvatarInputsBarEntity = false; + var micBarEntity, bubbleIconEntity; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + function onClicked(){ + onCreateAvatarInputsBarEntity = !onCreateAvatarInputsBarEntity; + button.editProperties({isActive: onCreateAvatarInputsBarEntity}); + var micBarDimensions = {x: 0.036, y: 0.048, z: 0.3}; + var bubbleIconDimensions = {x: 0.036, y: 0.036, z: 0.3}; + var micBarLocalPosition = {x: (-(micBarDimensions.x / 2)) - 0.2, y: -0.125, z: -0.5}; + var bubbleIconLocalPosition = {x: (micBarDimensions.x * 1.2 / 2) - 0.2, y: ((micBarDimensions.y - bubbleIconDimensions.y) / 2 - 0.125), z: -0.5}; + if (onCreateAvatarInputsBarEntity) { + var props = { + type: "Web", + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), + localPosition: micBarLocalPosition, + localRotation: Quat.lookAtSimple(Camera.orientation, micBarLocalPosition), + sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", + dimensions: micBarDimensions, + userData: { + grabbable: false + }, + }; + micBarEntity = Entities.addEntity(props, "local"); + var props = { + type: "Web", + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), + // y is 0.01 - (0.048 + 0.036) / 2 + // have 10% spacing separation between the entities + localPosition: bubbleIconLocalPosition, + localRotation: Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition), + sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", + dimensions: bubbleIconDimensions, + userData: { + grabbable: false + }, + }; + bubbleIconEntity = Entities.addEntity(props, "local"); + console.log("creating entity"); + } else { + console.log("deleting entity"); + Entities.deleteEntity(micBarEntity); + Entities.deleteEntity(bubbleIconEntity); + } + }; + + function setup() { + button = tablet.addButton({ + icon: "icons/tablet-icons/edit-i.svg", + activeIcon: "icons/tablet-icons/edit-a.svg", + text: buttonName + }); + button.clicked.connect(onClicked); + }; + + setup(); + + Script.scriptEnding.connect(function() { + if (micBarEntity) { + Entities.deleteEntity(micBarEntity); + } + if (bubbleIconEntity) { + Entities.deleteEntity(bubbleIconEntity); + } + tablet.removeButton(button); + }); + +}()); From d063882d3747cb42d665f6b9a4d479dd5a3420a4 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 18:14:56 -0800 Subject: [PATCH 18/78] adding user speaking level rotated vertically and muted symbol --- interface/resources/qml/hifi/audio/MicBar.qml | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index fba06ac987..6d428d9ab8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -13,6 +13,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import stylesUit 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { @@ -34,8 +35,8 @@ Rectangle { property bool standalone: false; property var dragTarget: null; - width: 240; - height: 50; + width: 44; + height: 44; radius: 5; @@ -48,8 +49,8 @@ Rectangle { // borders are painted over fill, so reduce the fill to fit inside the border Rectangle { color: standalone ? colors.fill : "#00000000"; - width: 236; - height: 46; + width: 40; + height: 40; radius: 5; @@ -98,7 +99,7 @@ Rectangle { readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; + readonly property string icon: muted ? muted : unmuted; } Item { @@ -106,7 +107,6 @@ Rectangle { anchors { left: parent.left; - leftMargin: 5; verticalCenter: parent.verticalCenter; } @@ -125,11 +125,11 @@ Rectangle { source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; - width: 30; - height: 30; + width: 21; + height: 24; anchors { left: parent.left; - leftMargin: 5; + leftMargin: 7; top: parent.top; topMargin: 5; } @@ -146,20 +146,20 @@ Rectangle { Item { id: status; - readonly property string color: muted ? colors.muted : colors.unmuted; + readonly property string color: colors.muted; visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - leftMargin: 50; - verticalCenter: parent.verticalCenter; + top: parent.bottom + topMargin: 5 } - width: 170; + width: icon.width; height: 8 - Text { + RalewaySemiBold { anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; @@ -197,11 +197,14 @@ Rectangle { Item { id: bar; - visible: !status.visible; + anchors { + right: parent.right; + rightMargin: 7; + verticalCenter: parent.verticalCenter; + } - anchors.fill: status; - - width: status.width; + width: 8; + height: 32; Rectangle { // base radius: 4; @@ -211,13 +214,12 @@ Rectangle { Rectangle { // mask id: mask; - width: gated ? 0 : parent.width * level; + height: parent.height * level; + width: parent.width; radius: 5; anchors { bottom: parent.bottom; bottomMargin: 0; - top: parent.top; - topMargin: 0; left: parent.left; leftMargin: 0; } @@ -227,10 +229,11 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(170, 0); + end: Qt.point(0, bar.height); + rotation: 180 gradient: Gradient { GradientStop { - position: 0; + position: 0.0; color: colors.greenStart; } GradientStop { @@ -238,8 +241,8 @@ Rectangle { color: colors.greenEnd; } GradientStop { - position: 1; - color: colors.yellow; + position: 1.0; + color: colors.red; } } } From 8e8ceaac475e6affd5ca199b035be171db28b8d6 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 18:19:23 -0800 Subject: [PATCH 19/78] moving file as MicBarApplication --- interface/resources/qml/hifi/audio/MicBar.qml | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 6d428d9ab8..cad64b9b74 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -13,7 +13,6 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import stylesUit 1.0 -import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { @@ -31,12 +30,12 @@ Rectangle { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } - + property bool standalone: false; property var dragTarget: null; - width: 44; - height: 44; + width: 240; + height: 50; radius: 5; @@ -49,8 +48,8 @@ Rectangle { // borders are painted over fill, so reduce the fill to fit inside the border Rectangle { color: standalone ? colors.fill : "#00000000"; - width: 40; - height: 40; + width: 236; + height: 46; radius: 5; @@ -107,6 +106,7 @@ Rectangle { anchors { left: parent.left; + leftMargin: 5; verticalCenter: parent.verticalCenter; } @@ -125,11 +125,11 @@ Rectangle { source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; - width: 21; - height: 24; + width: 30; + height: 30; anchors { left: parent.left; - leftMargin: 7; + leftMargin: 5; top: parent.top; topMargin: 5; } @@ -146,20 +146,20 @@ Rectangle { Item { id: status; - readonly property string color: colors.muted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - top: parent.bottom - topMargin: 5 + leftMargin: 50; + verticalCenter: parent.verticalCenter; } - width: icon.width; + width: 170; height: 8 - RalewaySemiBold { + Text { anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; @@ -197,14 +197,11 @@ Rectangle { Item { id: bar; - anchors { - right: parent.right; - rightMargin: 7; - verticalCenter: parent.verticalCenter; - } + visible: !status.visible; - width: 8; - height: 32; + anchors.fill: status; + + width: status.width; Rectangle { // base radius: 4; @@ -214,12 +211,13 @@ Rectangle { Rectangle { // mask id: mask; - height: parent.height * level; - width: parent.width; + width: gated ? 0 : parent.width * level; radius: 5; anchors { bottom: parent.bottom; bottomMargin: 0; + top: parent.top; + topMargin: 0; left: parent.left; leftMargin: 0; } @@ -229,11 +227,10 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(0, bar.height); - rotation: 180 + end: Qt.point(170, 0); gradient: Gradient { GradientStop { - position: 0.0; + position: 0; color: colors.greenStart; } GradientStop { @@ -241,17 +238,17 @@ Rectangle { color: colors.greenEnd; } GradientStop { - position: 1.0; - color: colors.red; + position: 1; + color: colors.yellow; } } } - + Rectangle { id: gatedIndicator; visible: gated && !AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: "#0080FF"; @@ -260,12 +257,12 @@ Rectangle { verticalCenter: parent.verticalCenter; } } - + Rectangle { id: clippingIndicator; visible: AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: colors.red; From db6bf46ee7deb17c6c0f5988b0f95f90ad84a912 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 17:38:11 -0800 Subject: [PATCH 20/78] adding more opacity, cleanup --- interface/resources/qml/hifi/audio/MicBarApplication.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index bfac278ee4..592843467b 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -53,6 +53,14 @@ Rectangle { micBar.opacity = rectOpacity; } + onLevelChanged: { + var rectOpacity = AudioScriptingInterface.muted && (level >= userSpeakingLevel)? 0.9 : 0.3; + if (mouseArea.containsMouse) { + rectOpacity = 0.5; + } + opacity = rectOpacity; + } + color: "#00000000"; border { width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; From 3b6c0b5b18219a46c7c9baab95ccb3cf65497d46 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 18:04:09 -0800 Subject: [PATCH 21/78] adding privacy shield files --- interface/src/ui/PrivacyShield.cpp | 12 ++++++++++++ interface/src/ui/PrivacyShield.h | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 interface/src/ui/PrivacyShield.cpp create mode 100644 interface/src/ui/PrivacyShield.h diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp new file mode 100644 index 0000000000..e1035ee5bb --- /dev/null +++ b/interface/src/ui/PrivacyShield.cpp @@ -0,0 +1,12 @@ +// +// PrivacyShield.h +// interface/src/ui +// +// Created by Wayne Chen on 2/27/19. +// 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 +// + +#include "PrivacyShield.cpp" diff --git a/interface/src/ui/PrivacyShield.h b/interface/src/ui/PrivacyShield.h new file mode 100644 index 0000000000..a609f2775b --- /dev/null +++ b/interface/src/ui/PrivacyShield.h @@ -0,0 +1,12 @@ +// +// PrivacyShield.h +// interface/src/ui +// +// Created by Wayne Chen on 2/27/19. +// 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 +// + +#pragma once From 49c3dfa52c67ede93e7cc30f03a23022a55e00f5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 18:04:58 -0800 Subject: [PATCH 22/78] fixing typos --- interface/src/ui/PrivacyShield.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp index e1035ee5bb..12687afbea 100644 --- a/interface/src/ui/PrivacyShield.cpp +++ b/interface/src/ui/PrivacyShield.cpp @@ -1,5 +1,5 @@ // -// PrivacyShield.h +// PrivacyShield.cpp // interface/src/ui // // Created by Wayne Chen on 2/27/19. @@ -9,4 +9,4 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "PrivacyShield.cpp" +#include "PrivacyShield.h" From 48b4fe37b4ccb53ac35de6028377aa199bb23b65 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 15:50:13 -0800 Subject: [PATCH 23/78] don't display gated state --- interface/resources/qml/hifi/audio/MicBarApplication.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 592843467b..812e581fe1 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -54,8 +54,8 @@ Rectangle { } onLevelChanged: { - var rectOpacity = AudioScriptingInterface.muted && (level >= userSpeakingLevel)? 0.9 : 0.3; - if (mouseArea.containsMouse) { + var rectOpacity = muted && (level >= userSpeakingLevel) ? 0.9 : 0.3; + if (mouseArea.containsMouse && rectOpacity != 0.9) { rectOpacity = 0.5; } opacity = rectOpacity; From 20e181cd82f9b6d180617dad1ac8c05de7fca98f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 16:51:19 -0800 Subject: [PATCH 24/78] staging avatar inputs for ignore radius --- interface/src/Application.cpp | 10 ++ interface/src/ui/PrivacyShield.cpp | 147 +++++++++++++++++++++++++++++ interface/src/ui/PrivacyShield.h | 35 +++++++ 3 files changed, 192 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9fd0781eef..bd8107793a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -211,6 +211,7 @@ #include "ui/UpdateDialog.h" #include "ui/DomainConnectionModel.h" #include "ui/Keyboard.h" +#include "ui/PrivacyShield.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -931,6 +932,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -2670,6 +2672,9 @@ void Application::cleanupBeforeQuit() { nodeList->getPacketReceiver().setShouldDropPackets(true); } + // destroy privacy shield before entity shutdown. + DependencyManager::get()->destroyPrivacyShield(); + getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) @@ -2748,6 +2753,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; @@ -5549,6 +5555,8 @@ void Application::resumeAfterLoginDialogActionTaken() { menu->getMenu("Developer")->setVisible(_developerMenuVisible); _myCamera.setMode(_previousCameraMode); cameraModeChanged(); + + DependencyManager::get()->createPrivacyShield(); } void Application::loadAvatarScripts(const QVector& urls) { @@ -6507,6 +6515,8 @@ void Application::update(float deltaTime) { updateLoginDialogPosition(); } + DependencyManager::get()->update(deltaTime); + { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp index 12687afbea..e8f61ff5bf 100644 --- a/interface/src/ui/PrivacyShield.cpp +++ b/interface/src/ui/PrivacyShield.cpp @@ -10,3 +10,150 @@ // #include "PrivacyShield.h" + +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "PathUtils.h" +#include "GLMHelpers.h" + +const int PRIVACY_SHIELD_VISIBLE_DURATION_MS = 3000; +const int PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS = 750; +const int PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS = 15000; +const float PRIVACY_SHIELD_HEIGHT_SCALE = 0.15f; + +PrivacyShield::PrivacyShield() { + auto usersScriptingInterface = DependencyManager::get(); + //connect(usersScriptingInterface.data(), &UsersScriptingInterface::ignoreRadiusEnabledChanged, [this](bool enabled) { + // onPrivacyShieldToggled(enabled); + //}); + //connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &PrivacyShield::enteredIgnoreRadius); +} + +void PrivacyShield::createPrivacyShield() { + // Affects bubble height + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarScale = myAvatar->getTargetScale(); + auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + auto avatarWorldPosition = myAvatar->getWorldPosition(); + auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + EntityItemProperties properties; + properties.setName("Privacy-Shield"); + properties.setModelURL(PathUtils::resourcesUrl("assets/models/Bubble-v14.fbx").toString()); + properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + properties.setPosition(glm::vec3(avatarWorldPosition.x, + -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); + properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + properties.setVisible(false); + + _localPrivacyShieldID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); + //_bubbleActivateSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl() + "assets/sounds/bubble.wav"); + + //onPrivacyShieldToggled(DependencyManager::get()->getIgnoreRadiusEnabled(), true); +} + +void PrivacyShield::destroyPrivacyShield() { + DependencyManager::get()->deleteEntity(_localPrivacyShieldID); +} + +void PrivacyShield::update(float deltaTime) { + if (_updateConnected) { + auto now = usecTimestampNow(); + auto delay = (now - _privacyShieldTimestamp); + auto privacyShieldAlpha = 1.0 - (delay / PRIVACY_SHIELD_VISIBLE_DURATION_MS); + if (privacyShieldAlpha > 0) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarScale = myAvatar->getTargetScale(); + auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + auto avatarWorldPosition = myAvatar->getWorldPosition(); + auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + EntityItemProperties properties; + properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); + if (delay < PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS) { + properties.setPosition(glm::vec3(avatarWorldPosition.x, + (-((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * avatarScale * 2.0 + + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setModelScale(glm::vec3(2.0, + ((1 - ((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * + (0.5 * (avatarScale + 1.0))), 2.0)); + } else { + properties.setPosition(glm::vec3(avatarWorldPosition.x, avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + } + DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); + } + else { + hidePrivacyShield(); + if (_updateConnected) { + _updateConnected = false; + } + } + } +} + +void PrivacyShield::enteredIgnoreRadius() { + showPrivacyShield(); + DependencyManager::get()->privacyShieldActivated(); +} + +void PrivacyShield::onPrivacyShieldToggled(bool enabled, bool doNotLog) { + if (!doNotLog) { + DependencyManager::get()->privacyShieldToggled(enabled); + } + if (enabled) { + showPrivacyShield(); + } else { + hidePrivacyShield(); + if (_updateConnected) { + _updateConnected = false; + } + } +} + +void PrivacyShield::showPrivacyShield() { + auto now = usecTimestampNow(); + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarScale = myAvatar->getTargetScale(); + auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); + auto avatarWorldPosition = myAvatar->getWorldPosition(); + auto avatarWorldOrientation = myAvatar->getWorldOrientation(); + if (now - _lastPrivacyShieldSoundTimestamp >= PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS) { + AudioInjectorOptions options; + options.position = avatarWorldPosition; + options.localOnly = true; + options.volume = 0.2f; + AudioInjector::playSoundAndDelete(_bubbleActivateSound, options); + _lastPrivacyShieldSoundTimestamp = now; + } + hidePrivacyShield(); + if (_updateConnected) { + _updateConnected = false; + } + + EntityItemProperties properties; + properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); + properties.setPosition(glm::vec3(avatarWorldPosition.x, + -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); + properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); + properties.setVisible(true); + + DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); + + _privacyShieldTimestamp = now; + _updateConnected = true; +} + +void PrivacyShield::hidePrivacyShield() { + EntityTreePointer entityTree = qApp->getEntities()->getTree(); + EntityItemPointer privacyShieldEntity = entityTree->findEntityByEntityItemID(EntityItemID(_localPrivacyShieldID)); + if (privacyShieldEntity) { + privacyShieldEntity->setVisible(false); + } +} diff --git a/interface/src/ui/PrivacyShield.h b/interface/src/ui/PrivacyShield.h index a609f2775b..5aecb661f7 100644 --- a/interface/src/ui/PrivacyShield.h +++ b/interface/src/ui/PrivacyShield.h @@ -10,3 +10,38 @@ // #pragma once + +#include +#include +#include + +#include +#include + +class PrivacyShield : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + PrivacyShield(); + void createPrivacyShield(); + void destroyPrivacyShield(); + + bool isVisible() const { return _visible; } + void update(float deltaTime); + +protected slots: + void enteredIgnoreRadius(); + void onPrivacyShieldToggled(bool enabled, bool doNotLog = false); + +private: + void showPrivacyShield(); + void hidePrivacyShield(); + + SharedSoundPointer _bubbleActivateSound; + QUuid _localPrivacyShieldID; + quint64 _privacyShieldTimestamp; + quint64 _lastPrivacyShieldSoundTimestamp; + bool _visible { false }; + bool _updateConnected { false }; +}; From 84fcd50b3b3e0b3231b6fe80cc56b19af8895515 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 18:14:56 -0800 Subject: [PATCH 25/78] adding user speaking level rotated vertically and muted symbol --- interface/resources/qml/hifi/audio/MicBar.qml | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index cad64b9b74..4734da8771 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -13,6 +13,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import stylesUit 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { @@ -34,8 +35,8 @@ Rectangle { property bool standalone: false; property var dragTarget: null; - width: 240; - height: 50; + width: 44; + height: 44; radius: 5; @@ -48,8 +49,8 @@ Rectangle { // borders are painted over fill, so reduce the fill to fit inside the border Rectangle { color: standalone ? colors.fill : "#00000000"; - width: 236; - height: 46; + width: 40; + height: 40; radius: 5; @@ -106,7 +107,6 @@ Rectangle { anchors { left: parent.left; - leftMargin: 5; verticalCenter: parent.verticalCenter; } @@ -125,11 +125,11 @@ Rectangle { source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; - width: 30; - height: 30; + width: 21; + height: 24; anchors { left: parent.left; - leftMargin: 5; + leftMargin: 7; top: parent.top; topMargin: 5; } @@ -146,20 +146,20 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: colors.muted; visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - leftMargin: 50; - verticalCenter: parent.verticalCenter; + top: parent.bottom + topMargin: 5 } - width: 170; + width: icon.width; height: 8 - Text { + RalewaySemiBold { anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; @@ -197,11 +197,14 @@ Rectangle { Item { id: bar; - visible: !status.visible; + anchors { + right: parent.right; + rightMargin: 7; + verticalCenter: parent.verticalCenter; + } - anchors.fill: status; - - width: status.width; + width: 8; + height: 32; Rectangle { // base radius: 4; @@ -211,13 +214,12 @@ Rectangle { Rectangle { // mask id: mask; - width: gated ? 0 : parent.width * level; + height: parent.height * level; + width: parent.width; radius: 5; anchors { bottom: parent.bottom; bottomMargin: 0; - top: parent.top; - topMargin: 0; left: parent.left; leftMargin: 0; } @@ -227,10 +229,11 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(170, 0); + end: Qt.point(0, bar.height); + rotation: 180 gradient: Gradient { GradientStop { - position: 0; + position: 0.0; color: colors.greenStart; } GradientStop { @@ -238,8 +241,8 @@ Rectangle { color: colors.greenEnd; } GradientStop { - position: 1; - color: colors.yellow; + position: 1.0; + color: colors.red; } } } From cd7b6e7ed08a6ac6d8c02a582ecb69c25b5c2e18 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Feb 2019 18:19:23 -0800 Subject: [PATCH 26/78] moving file as MicBarApplication --- interface/resources/qml/hifi/audio/MicBar.qml | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 4734da8771..cad64b9b74 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -13,7 +13,6 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import stylesUit 1.0 -import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { @@ -35,8 +34,8 @@ Rectangle { property bool standalone: false; property var dragTarget: null; - width: 44; - height: 44; + width: 240; + height: 50; radius: 5; @@ -49,8 +48,8 @@ Rectangle { // borders are painted over fill, so reduce the fill to fit inside the border Rectangle { color: standalone ? colors.fill : "#00000000"; - width: 40; - height: 40; + width: 236; + height: 46; radius: 5; @@ -107,6 +106,7 @@ Rectangle { anchors { left: parent.left; + leftMargin: 5; verticalCenter: parent.verticalCenter; } @@ -125,11 +125,11 @@ Rectangle { source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; - width: 21; - height: 24; + width: 30; + height: 30; anchors { left: parent.left; - leftMargin: 7; + leftMargin: 5; top: parent.top; topMargin: 5; } @@ -146,20 +146,20 @@ Rectangle { Item { id: status; - readonly property string color: colors.muted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; - top: parent.bottom - topMargin: 5 + leftMargin: 50; + verticalCenter: parent.verticalCenter; } - width: icon.width; + width: 170; height: 8 - RalewaySemiBold { + Text { anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter; @@ -197,14 +197,11 @@ Rectangle { Item { id: bar; - anchors { - right: parent.right; - rightMargin: 7; - verticalCenter: parent.verticalCenter; - } + visible: !status.visible; - width: 8; - height: 32; + anchors.fill: status; + + width: status.width; Rectangle { // base radius: 4; @@ -214,12 +211,13 @@ Rectangle { Rectangle { // mask id: mask; - height: parent.height * level; - width: parent.width; + width: gated ? 0 : parent.width * level; radius: 5; anchors { bottom: parent.bottom; bottomMargin: 0; + top: parent.top; + topMargin: 0; left: parent.left; leftMargin: 0; } @@ -229,11 +227,10 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(0, bar.height); - rotation: 180 + end: Qt.point(170, 0); gradient: Gradient { GradientStop { - position: 0.0; + position: 0; color: colors.greenStart; } GradientStop { @@ -241,8 +238,8 @@ Rectangle { color: colors.greenEnd; } GradientStop { - position: 1.0; - color: colors.red; + position: 1; + color: colors.yellow; } } } From f80f4b2fd4db894e2b0f07d48a8ba9b98bdd2d0f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 27 Feb 2019 14:54:13 -0800 Subject: [PATCH 27/78] adding working bubble icon --- interface/src/ui/AvatarInputs.cpp | 2 -- interface/src/ui/AvatarInputs.h | 23 ----------------------- 2 files changed, 25 deletions(-) diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 80604f354b..352db37a78 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -32,9 +32,7 @@ AvatarInputs* AvatarInputs::getInstance() { AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); auto nodeList = DependencyManager::get(); - auto usersScriptingInterface = DependencyManager::get(); connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged); - connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &AvatarInputs::enteredIgnoreRadiusChanged); } #define AI_UPDATE(name, src) \ diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index f53adc1749..1feb054147 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -43,7 +43,6 @@ class AvatarInputs : public QObject { Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged) - //Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged) public: static AvatarInputs* getInstance(); @@ -59,7 +58,6 @@ public: void update(); bool showAudioTools() const { return _showAudioTools; } bool getIgnoreRadiusEnabled() const; - //bool getEnteredIgnoreRadius() const; public slots: @@ -97,20 +95,6 @@ signals: */ void showAudioToolsChanged(bool show); - /**jsdoc - * @function AvatarInputs.avatarEnteredIgnoreRadius - * @param {QUuid} avatarID - * @returns {Signal} - */ - void avatarEnteredIgnoreRadius(QUuid avatarID); - - /**jsdoc - * @function AvatarInputs.avatarLeftIgnoreRadius - * @param {QUuid} avatarID - * @returns {Signal} - */ - void avatarLeftIgnoreRadius(QUuid avatarID); - /**jsdoc * @function AvatarInputs.ignoreRadiusEnabledChanged * @param {boolean} enabled @@ -118,13 +102,6 @@ signals: */ void ignoreRadiusEnabledChanged(bool enabled); - /**jsdoc - * @function AvatarInputs.enteredIgnoreRadiusChanged - * @param {boolean} enabled - * @returns {Signal} - */ - void enteredIgnoreRadiusChanged(); - protected: /**jsdoc From e5133d6f4d3fbccba641e088a2be5e03fd91ef43 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 16:48:17 -0800 Subject: [PATCH 28/78] fixing muted text --- interface/resources/icons/tablet-icons/mic.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/icons/tablet-icons/mic.svg b/interface/resources/icons/tablet-icons/mic.svg index 30b46d18dd..c961351b14 100644 --- a/interface/resources/icons/tablet-icons/mic.svg +++ b/interface/resources/icons/tablet-icons/mic.svg @@ -59,4 +59,4 @@ d="m 27.9,20.9 c 0,0 0,-3.6 0,-3.8 0,-0.7 -0.6,-1.2 -1.3,-1.2 -0.7,0 -1.2,0.6 -1.2,1.3 0,0.2 0,3.4 0,3.7 0,2.6 -2.4,4.8 -5.3,4.8 -2.9,0 -5.3,-2.1 -5.3,-4.8 0,-0.3 0,-3.5 0,-3.8 0,-0.7 -0.5,-1.3 -1.2,-1.3 -0.7,0 -1.3,0.5 -1.3,1.2 0,0.2 0,3.9 0,3.9 0,3.6 2.9,6.6 6.6,7.2 l 0,2.4 -3.1,0 c -0.7,0 -1.3,0.6 -1.3,1.3 0,0.7 0.6,1.3 1.3,1.3 l 8.8,0 c 0.7,0 1.3,-0.6 1.3,-1.3 0,-0.7 -0.6,-1.3 -1.3,-1.3 l -3.2,0 0,-2.4 c 3.6,-0.5 6.5,-3.5 6.5,-7.2 z" id="path6952" inkscape:connector-curvature="0" - style="fill:#ffffff" /> \ No newline at end of file + style="fill:#ffffff" /> From 8e6913dde1ae7b3803a829b041263cf698b1418d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Feb 2019 16:51:19 -0800 Subject: [PATCH 29/78] staging avatar inputs for ignore radius --- interface/src/ui/AvatarInputs.cpp | 2 ++ interface/src/ui/AvatarInputs.h | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 352db37a78..80604f354b 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -32,7 +32,9 @@ AvatarInputs* AvatarInputs::getInstance() { AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) { _showAudioTools = showAudioToolsSetting.get(); auto nodeList = DependencyManager::get(); + auto usersScriptingInterface = DependencyManager::get(); connect(nodeList.data(), &NodeList::ignoreRadiusEnabledChanged, this, &AvatarInputs::ignoreRadiusEnabledChanged); + connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &AvatarInputs::enteredIgnoreRadiusChanged); } #define AI_UPDATE(name, src) \ diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 1feb054147..f53adc1749 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -43,6 +43,7 @@ class AvatarInputs : public QObject { Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) Q_PROPERTY(bool ignoreRadiusEnabled READ getIgnoreRadiusEnabled NOTIFY ignoreRadiusEnabledChanged) + //Q_PROPERTY(bool enteredIgnoreRadius READ getEnteredIgnoreRadius NOTIFY enteredIgnoreRadiusChanged) public: static AvatarInputs* getInstance(); @@ -58,6 +59,7 @@ public: void update(); bool showAudioTools() const { return _showAudioTools; } bool getIgnoreRadiusEnabled() const; + //bool getEnteredIgnoreRadius() const; public slots: @@ -95,6 +97,20 @@ signals: */ void showAudioToolsChanged(bool show); + /**jsdoc + * @function AvatarInputs.avatarEnteredIgnoreRadius + * @param {QUuid} avatarID + * @returns {Signal} + */ + void avatarEnteredIgnoreRadius(QUuid avatarID); + + /**jsdoc + * @function AvatarInputs.avatarLeftIgnoreRadius + * @param {QUuid} avatarID + * @returns {Signal} + */ + void avatarLeftIgnoreRadius(QUuid avatarID); + /**jsdoc * @function AvatarInputs.ignoreRadiusEnabledChanged * @param {boolean} enabled @@ -102,6 +118,13 @@ signals: */ void ignoreRadiusEnabledChanged(bool enabled); + /**jsdoc + * @function AvatarInputs.enteredIgnoreRadiusChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void enteredIgnoreRadiusChanged(); + protected: /**jsdoc From d0116abc5271fccc15ae8e6f9371faf92120332c Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 14 Mar 2019 10:28:18 -0700 Subject: [PATCH 30/78] separating out bubble icon qml - adding support for transparency --- interface/resources/qml/hifi/audio/MicBar.qml | 16 +++---- .../qml/hifi/audio/MicBarApplication.qml | 6 ++- interface/src/Application.cpp | 10 ---- interface/src/ui/PrivacyShield.h | 47 ------------------- scripts/defaultScripts.js | 3 +- 5 files changed, 13 insertions(+), 69 deletions(-) delete mode 100644 interface/src/ui/PrivacyShield.h diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index cad64b9b74..fb52f8bc5e 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -30,7 +30,7 @@ Rectangle { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } - + property bool standalone: false; property var dragTarget: null; @@ -146,7 +146,7 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: muted ? colors.muted : colors.unmuted; visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); @@ -243,12 +243,12 @@ Rectangle { } } } - + Rectangle { id: gatedIndicator; visible: gated && !AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: "#0080FF"; @@ -257,12 +257,12 @@ Rectangle { verticalCenter: parent.verticalCenter; } } - + Rectangle { id: clippingIndicator; visible: AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: colors.red; diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 812e581fe1..f2839aee1a 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -55,10 +55,12 @@ Rectangle { onLevelChanged: { var rectOpacity = muted && (level >= userSpeakingLevel) ? 0.9 : 0.3; - if (mouseArea.containsMouse && rectOpacity != 0.9) { + if (pushToTalk && !pushingToTalk) { + rectOpacity = (level >= userSpeakingLevel) ? 0.9 : 0.7; + } else if (mouseArea.containsMouse && rectOpacity != 0.9) { rectOpacity = 0.5; } - opacity = rectOpacity; + micBar.opacity = rectOpacity; } color: "#00000000"; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bd8107793a..9fd0781eef 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -211,7 +211,6 @@ #include "ui/UpdateDialog.h" #include "ui/DomainConnectionModel.h" #include "ui/Keyboard.h" -#include "ui/PrivacyShield.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -932,7 +931,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); return previousSessionCrashed; } @@ -2672,9 +2670,6 @@ void Application::cleanupBeforeQuit() { nodeList->getPacketReceiver().setShouldDropPackets(true); } - // destroy privacy shield before entity shutdown. - DependencyManager::get()->destroyPrivacyShield(); - getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) @@ -2753,7 +2748,6 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - DependencyManager::destroy(); DependencyManager::destroy(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; @@ -5555,8 +5549,6 @@ void Application::resumeAfterLoginDialogActionTaken() { menu->getMenu("Developer")->setVisible(_developerMenuVisible); _myCamera.setMode(_previousCameraMode); cameraModeChanged(); - - DependencyManager::get()->createPrivacyShield(); } void Application::loadAvatarScripts(const QVector& urls) { @@ -6515,8 +6507,6 @@ void Application::update(float deltaTime) { updateLoginDialogPosition(); } - DependencyManager::get()->update(deltaTime); - { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); diff --git a/interface/src/ui/PrivacyShield.h b/interface/src/ui/PrivacyShield.h deleted file mode 100644 index 5aecb661f7..0000000000 --- a/interface/src/ui/PrivacyShield.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// PrivacyShield.h -// interface/src/ui -// -// Created by Wayne Chen on 2/27/19. -// 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 -// - -#pragma once - -#include -#include -#include - -#include -#include - -class PrivacyShield : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - PrivacyShield(); - void createPrivacyShield(); - void destroyPrivacyShield(); - - bool isVisible() const { return _visible; } - void update(float deltaTime); - -protected slots: - void enteredIgnoreRadius(); - void onPrivacyShieldToggled(bool enabled, bool doNotLog = false); - -private: - void showPrivacyShield(); - void hidePrivacyShield(); - - SharedSoundPointer _bubbleActivateSound; - QUuid _localPrivacyShieldID; - quint64 _privacyShieldTimestamp; - quint64 _lastPrivacyShieldSoundTimestamp; - bool _visible { false }; - bool _updateConnected { false }; -}; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 0d9799a035..bd7e79dffc 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,8 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js", - "system/createAvatarInputsBarEntity.js" + "system/miniTablet.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", From 87de95c63b47bf23b24cc71662db9e6dc7ddfbdd Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 14 Mar 2019 15:05:26 -0700 Subject: [PATCH 31/78] adding test script for creating avatar input bar --- scripts/defaultScripts.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index bd7e79dffc..0d9799a035 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js" + "system/miniTablet.js", + "system/createAvatarInputsBarEntity.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", From 44ed9e607b24a59892ec7b066772ea85adae9998 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 19 Mar 2019 09:33:38 -0700 Subject: [PATCH 32/78] cancelling out roll and pitch --- scripts/system/createAvatarInputsBarEntity.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index babf519035..b32085da86 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -21,7 +21,7 @@ parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), localPosition: micBarLocalPosition, - localRotation: Quat.lookAtSimple(Camera.orientation, micBarLocalPosition), + localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", dimensions: micBarDimensions, userData: { @@ -36,7 +36,7 @@ // y is 0.01 - (0.048 + 0.036) / 2 // have 10% spacing separation between the entities localPosition: bubbleIconLocalPosition, - localRotation: Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition), + localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", dimensions: bubbleIconDimensions, userData: { @@ -44,9 +44,7 @@ }, }; bubbleIconEntity = Entities.addEntity(props, "local"); - console.log("creating entity"); } else { - console.log("deleting entity"); Entities.deleteEntity(micBarEntity); Entities.deleteEntity(bubbleIconEntity); } From ee1a14505a67f2cbbfe10ba5d7f59063a252eb2f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 19 Mar 2019 09:44:08 -0700 Subject: [PATCH 33/78] adding constants, removing entity creation in cpp --- interface/src/Application.cpp | 18 +++++++++--------- scripts/system/createAvatarInputsBarEntity.js | 16 ++++++++++------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9fd0781eef..5b0c379c64 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1298,15 +1298,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &Application::activeDisplayPluginChanged, this, [&](){ #if !defined(Q_OS_ANDROID) if (!getLoginDialogPoppedUp() && _desktopRootItemCreated) { - if (isHMDMode()) { - createAvatarInputsBar(); - auto offscreenUi = getOffscreenUI(); - offscreenUi->hide(AVATAR_INPUTS_BAR_QML.toString()); - } else { - destroyAvatarInputsBar(); - auto offscreenUi = getOffscreenUI(); - offscreenUi->show(AVATAR_INPUTS_BAR_QML.toString(), "AvatarInputsBar"); - } +/* if (isHMDMode()) {*/ + //createAvatarInputsBar(); + //auto offscreenUi = getOffscreenUI(); + //offscreenUi->hide(AVATAR_INPUTS_BAR_QML.toString()); + //} else { + //destroyAvatarInputsBar(); + //auto offscreenUi = getOffscreenUI(); + //offscreenUi->show(AVATAR_INPUTS_BAR_QML.toString(), "AvatarInputsBar"); + /*}*/ } #endif }); diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index b32085da86..8c4d78d32d 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -11,10 +11,16 @@ function onClicked(){ onCreateAvatarInputsBarEntity = !onCreateAvatarInputsBarEntity; button.editProperties({isActive: onCreateAvatarInputsBarEntity}); - var micBarDimensions = {x: 0.036, y: 0.048, z: 0.3}; - var bubbleIconDimensions = {x: 0.036, y: 0.036, z: 0.3}; - var micBarLocalPosition = {x: (-(micBarDimensions.x / 2)) - 0.2, y: -0.125, z: -0.5}; - var bubbleIconLocalPosition = {x: (micBarDimensions.x * 1.2 / 2) - 0.2, y: ((micBarDimensions.y - bubbleIconDimensions.y) / 2 - 0.125), z: -0.5}; + // QML NATURAL DIMENSIONS + var MIC_BAR_DIMENSIONS = {x: 0.036, y: 0.048, z: 0.3}; + var BUBBLE_ICON_DIMENSIONS = {x: 0.036, y: 0.036, z: 0.3}; + // CONSTANTS + var LOCAL_POSITION_X_OFFSET = -0.2; + var LOCAL_POSITION_Y_OFFSET = -0.125; + var LOCAL_POSITION_Z_OFFSET = -0.5; + // POSITIONS + var micBarLocalPosition = {x: (-(micBarDimensions.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; + var bubbleIconLocalPosition = {x: (micBarDimensions.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((micBarDimensions.y - bubbleIconDimensions.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; if (onCreateAvatarInputsBarEntity) { var props = { type: "Web", @@ -33,8 +39,6 @@ type: "Web", parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), - // y is 0.01 - (0.048 + 0.036) / 2 - // have 10% spacing separation between the entities localPosition: bubbleIconLocalPosition, localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", From 4cbe8cd4d1b39989c4a3c5a81d1a9b445f1f41f6 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 19 Mar 2019 10:55:00 -0700 Subject: [PATCH 34/78] Adding alpha to entities to enable transparency --- scripts/system/createAvatarInputsBarEntity.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index 8c4d78d32d..c14bf433d7 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -29,6 +29,7 @@ localPosition: micBarLocalPosition, localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", + alpha: 0.9, dimensions: micBarDimensions, userData: { grabbable: false @@ -42,6 +43,7 @@ localPosition: bubbleIconLocalPosition, localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", + alpha: 0.9, dimensions: bubbleIconDimensions, userData: { grabbable: false From b6ebdb5315fdc24f18255182175acdbd9791f104 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 19 Mar 2019 13:49:59 -0700 Subject: [PATCH 35/78] alpha to max value of detecting transparency --- scripts/system/createAvatarInputsBarEntity.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index c14bf433d7..c64c8e35c5 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -29,7 +29,8 @@ localPosition: micBarLocalPosition, localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", - alpha: 0.9, + // cutoff alpha for detecting transparency + alpha: 0.98, dimensions: micBarDimensions, userData: { grabbable: false @@ -43,7 +44,8 @@ localPosition: bubbleIconLocalPosition, localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", - alpha: 0.9, + // cutoff alpha for detecting transparency + alpha: 0.98, dimensions: bubbleIconDimensions, userData: { grabbable: false From cb715c3e55ee84f8ab2047853f34f7e031b1bf24 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 20 Mar 2019 09:39:14 -0700 Subject: [PATCH 36/78] fixing typos --- scripts/system/createAvatarInputsBarEntity.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index c64c8e35c5..064bfa33e8 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -19,8 +19,8 @@ var LOCAL_POSITION_Y_OFFSET = -0.125; var LOCAL_POSITION_Z_OFFSET = -0.5; // POSITIONS - var micBarLocalPosition = {x: (-(micBarDimensions.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; - var bubbleIconLocalPosition = {x: (micBarDimensions.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((micBarDimensions.y - bubbleIconDimensions.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; + var micBarLocalPosition = {x: (-(MIC_BAR_DIMENSIONS.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; + var bubbleIconLocalPosition = {x: (MIC_BAR_DIMENSIONS.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; if (onCreateAvatarInputsBarEntity) { var props = { type: "Web", @@ -31,7 +31,7 @@ sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", // cutoff alpha for detecting transparency alpha: 0.98, - dimensions: micBarDimensions, + dimensions: MIC_BAR_DIMENSIONS, userData: { grabbable: false }, @@ -46,7 +46,7 @@ sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", // cutoff alpha for detecting transparency alpha: 0.98, - dimensions: bubbleIconDimensions, + dimensions: BUBBLE_ICON_DIMENSIONS, userData: { grabbable: false }, From 6cf8b06c6daa06c18e64b36af439ab6b64b45112 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 20 Mar 2019 17:39:32 -0700 Subject: [PATCH 37/78] more wip on adding qml to adjusting ui position --- .../qml/hifi/EditAvatarInputsBar.qml | 37 +++++++++++++++++++ scripts/system/createAvatarInputsBarEntity.js | 13 +++++++ 2 files changed, 50 insertions(+) create mode 100644 interface/resources/qml/hifi/EditAvatarInputsBar.qml diff --git a/interface/resources/qml/hifi/EditAvatarInputsBar.qml b/interface/resources/qml/hifi/EditAvatarInputsBar.qml new file mode 100644 index 0000000000..d9ba3a6fcc --- /dev/null +++ b/interface/resources/qml/hifi/EditAvatarInputsBar.qml @@ -0,0 +1,37 @@ +// +// EditAvatarInputsBar.qml +// qml/hifi +// +// Audio setup +// +// Created by Wayne Chen on 3/20/2019 +// 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 +// + +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../windows" + +Rectangle { + id: editRect + + HifiConstants { id: hifi; } + + color: hifi.colors.baseGray; + + signal sendToScript(var message); + function emitSendToScript(message) { + sendToScript(message); + } + + function fromScript(message) { + } + +} diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index 064bfa33e8..0b8fde4bb3 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -1,12 +1,20 @@ "use strict"; (function(){ + var AppUi = Script.require("appUi"); + + var ui; var button; var buttonName = "AVBAR"; var onCreateAvatarInputsBarEntity = false; var micBarEntity, bubbleIconEntity; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml"; + + function fromQml(message) { + console.log("message from QML: " + JSON.stringify(message)); + }; function onClicked(){ onCreateAvatarInputsBarEntity = !onCreateAvatarInputsBarEntity; @@ -24,6 +32,7 @@ if (onCreateAvatarInputsBarEntity) { var props = { type: "Web", + name: "AvatarInputsMicBarEntity", parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), localPosition: micBarLocalPosition, @@ -32,6 +41,7 @@ // cutoff alpha for detecting transparency alpha: 0.98, dimensions: MIC_BAR_DIMENSIONS, + drawInFront: true, userData: { grabbable: false }, @@ -39,6 +49,7 @@ micBarEntity = Entities.addEntity(props, "local"); var props = { type: "Web", + name: "AvatarInputsBubbleIconEntity", parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), localPosition: bubbleIconLocalPosition, @@ -47,11 +58,13 @@ // cutoff alpha for detecting transparency alpha: 0.98, dimensions: BUBBLE_ICON_DIMENSIONS, + drawInFront: true, userData: { grabbable: false }, }; bubbleIconEntity = Entities.addEntity(props, "local"); + tablet.loadQMLSource(AVATAR_INPUTS_EDIT_QML_SOURCE); } else { Entities.deleteEntity(micBarEntity); Entities.deleteEntity(bubbleIconEntity); From 584fa1f17b1c8b98c8b9d9324b2ab51fe0982a50 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 20 Mar 2019 18:07:50 -0700 Subject: [PATCH 38/78] more wip --- .../qml/hifi/EditAvatarInputsBar.qml | 83 +++++++++++++++++++ scripts/system/createAvatarInputsBarEntity.js | 17 ++-- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/EditAvatarInputsBar.qml b/interface/resources/qml/hifi/EditAvatarInputsBar.qml index d9ba3a6fcc..d34537bb0e 100644 --- a/interface/resources/qml/hifi/EditAvatarInputsBar.qml +++ b/interface/resources/qml/hifi/EditAvatarInputsBar.qml @@ -28,10 +28,93 @@ Rectangle { signal sendToScript(var message); function emitSendToScript(message) { + console.log("sending to script"); + console.log(JSON.stringify(message)); sendToScript(message); } function fromScript(message) { } + RalewayRegular { + id: title; + color: hifi.colors.white; + text: qsTr("Avatar Inputs Persistent UI Settings") + size: 20 + font.bold: true + anchors { + top: parent.top + left: parent.left + leftMargin: (parent.width - width) / 2 + } + } + + HifiControlsUit.Slider { + id: xSlider + anchors { + top: title.bottom + topMargin: 50 + left: parent.left + } + label: "X OFFSET" + maximumValue: 1.0 + minimumValue: -1.0 + stepSize: 0.05 + value: -0.2 + width: 300 + onValueChanged: { + emitSendToScript({ + "method": "reposition", + "x": value + }); + } + } + + HifiControlsUit.Slider { + id: ySlider + anchors { + top: xSlider.bottom + topMargin: 50 + left: parent.left + } + label: "Y OFFSET" + maximumValue: 1.0 + minimumValue: -1.0 + stepSize: 0.05 + value: -0.125 + width: 300 + onValueChanged: { + emitSendToScript({ + "method": "reposition", + "y": value + }); + } + } + + HifiControlsUit.Slider { + anchors { + top: ySlider.bottom + topMargin: 50 + left: parent.left + } + label: "Y OFFSET" + maximumValue: 0.0 + minimumValue: -1.0 + stepSize: 0.05 + value: -0.5 + width: 300 + onValueChanged: { + emitSendToScript({ + "method": "reposition", + "z": value + }); + } + } + + HifiControlsUit.Button { + id: setVisibleButton + } + + HifiControlsUit.Button { + } } diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index 0b8fde4bb3..5ffba2c029 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -13,7 +13,7 @@ var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml"; function fromQml(message) { - console.log("message from QML: " + JSON.stringify(message)); + print("message from QML: " + JSON.stringify(message)); }; function onClicked(){ @@ -72,10 +72,17 @@ }; function setup() { - button = tablet.addButton({ - icon: "icons/tablet-icons/edit-i.svg", - activeIcon: "icons/tablet-icons/edit-a.svg", - text: buttonName + // button = tablet.addButton({ + // icon: "icons/tablet-icons/edit-i.svg", + // activeIcon: "icons/tablet-icons/edit-a.svg", + // text: buttonName + // }); + ui = new AppUi({ + buttonName: "AVBAR", + home: Script.resourcesPath() + "qml/hifi/EditAvatarInputsBar.qml", + onMessage: fromQml, + // normalButton: "icons/tablet-icons/avatar-i.svg", + // activeButton: "icons/tablet-icons/avatar-a.svg", }); button.clicked.connect(onClicked); }; From 554a144b0efc43ed435fae9c2011502dddc4cfc4 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 20 Mar 2019 21:55:36 -0700 Subject: [PATCH 39/78] adding tool to change avatar inputs properties --- .../qml/hifi/EditAvatarInputsBar.qml | 38 +++- scripts/system/createAvatarInputsBarEntity.js | 175 ++++++++++-------- 2 files changed, 138 insertions(+), 75 deletions(-) diff --git a/interface/resources/qml/hifi/EditAvatarInputsBar.qml b/interface/resources/qml/hifi/EditAvatarInputsBar.qml index d34537bb0e..bbf3652a92 100644 --- a/interface/resources/qml/hifi/EditAvatarInputsBar.qml +++ b/interface/resources/qml/hifi/EditAvatarInputsBar.qml @@ -55,6 +55,7 @@ Rectangle { top: title.bottom topMargin: 50 left: parent.left + leftMargin: 20 } label: "X OFFSET" maximumValue: 1.0 @@ -76,6 +77,7 @@ Rectangle { top: xSlider.bottom topMargin: 50 left: parent.left + leftMargin: 20 } label: "Y OFFSET" maximumValue: 1.0 @@ -92,12 +94,14 @@ Rectangle { } HifiControlsUit.Slider { + id: zSlider anchors { top: ySlider.bottom topMargin: 50 left: parent.left + leftMargin: 20 } - label: "Y OFFSET" + label: "Z OFFSET" maximumValue: 0.0 minimumValue: -1.0 stepSize: 0.05 @@ -112,9 +116,39 @@ Rectangle { } HifiControlsUit.Button { - id: setVisibleButton + id: setVisibleButton; + text: setVisible ? "SET INVISIBLE" : "SET VISIBLE"; + width: 300; + property bool setVisible: true; + anchors { + top: zSlider.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + onClicked: { + setVisible = !setVisible; + emitSendToScript({ + "method": "setVisible", + "visible": setVisible + }); + } } HifiControlsUit.Button { + id: printButton; + text: "PRINT POSITIONS"; + width: 300; + anchors { + top: setVisibleButton.bottom + topMargin: 50 + left: parent.left + leftMargin: 20 + } + onClicked: { + emitSendToScript({ + "method": "print", + }); + } } } diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index 5ffba2c029..35c50416ed 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -5,98 +5,127 @@ var ui; - var button; - var buttonName = "AVBAR"; var onCreateAvatarInputsBarEntity = false; var micBarEntity, bubbleIconEntity; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml"; - function fromQml(message) { - print("message from QML: " + JSON.stringify(message)); - }; + // QML NATURAL DIMENSIONS + var MIC_BAR_DIMENSIONS = {x: 0.036, y: 0.048, z: 0.3}; + var BUBBLE_ICON_DIMENSIONS = {x: 0.036, y: 0.036, z: 0.3}; + // CONSTANTS + var LOCAL_POSITION_X_OFFSET = -0.2; + var LOCAL_POSITION_Y_OFFSET = -0.125; + var LOCAL_POSITION_Z_OFFSET = -0.5; - function onClicked(){ - onCreateAvatarInputsBarEntity = !onCreateAvatarInputsBarEntity; - button.editProperties({isActive: onCreateAvatarInputsBarEntity}); - // QML NATURAL DIMENSIONS - var MIC_BAR_DIMENSIONS = {x: 0.036, y: 0.048, z: 0.3}; - var BUBBLE_ICON_DIMENSIONS = {x: 0.036, y: 0.036, z: 0.3}; - // CONSTANTS - var LOCAL_POSITION_X_OFFSET = -0.2; - var LOCAL_POSITION_Y_OFFSET = -0.125; - var LOCAL_POSITION_Z_OFFSET = -0.5; - // POSITIONS - var micBarLocalPosition = {x: (-(MIC_BAR_DIMENSIONS.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; - var bubbleIconLocalPosition = {x: (MIC_BAR_DIMENSIONS.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; - if (onCreateAvatarInputsBarEntity) { - var props = { - type: "Web", - name: "AvatarInputsMicBarEntity", - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), - localPosition: micBarLocalPosition, - localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)), - sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", - // cutoff alpha for detecting transparency - alpha: 0.98, - dimensions: MIC_BAR_DIMENSIONS, - drawInFront: true, - userData: { - grabbable: false - }, + function fromQml(message) { + if (message.method === "reposition") { + var micBarLocalPosition = Entities.getEntityProperties(micBarEntity).localPosition; + var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition; + var newMicBarLocalPosition, newBubbleIconLocalPosition; + if (message.x !== undefined) { + newMicBarLocalPosition = { x: -((MIC_BAR_DIMENSIONS.x) / 2) - message.x, y: micBarLocalPosition.y, z: micBarLocalPosition.z }; + newBubbleIconLocalPosition = { x: ((MIC_BAR_DIMENSIONS.x) * 1.2 / 2) - message.x, y: bubbleIconLocalPosition.y, z: bubbleIconLocalPosition.z }; + } else if (message.y !== undefined) { + newMicBarLocalPosition = { x: micBarLocalPosition.x, y: message.y, z: micBarLocalPosition.z }; + newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + message.y), z: bubbleIconLocalPosition.z }; + } else if (message.z !== undefined) { + newMicBarLocalPosition = { x: micBarLocalPosition.x, y: micBarLocalPosition.y, z: message.z }; + newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: bubbleIconLocalPosition.y, z: message.z }; + } + var micBarProps = { + localPosition: newMicBarLocalPosition }; - micBarEntity = Entities.addEntity(props, "local"); - var props = { - type: "Web", - name: "AvatarInputsBubbleIconEntity", - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), - localPosition: bubbleIconLocalPosition, - localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), - sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", - // cutoff alpha for detecting transparency - alpha: 0.98, - dimensions: BUBBLE_ICON_DIMENSIONS, - drawInFront: true, - userData: { - grabbable: false - }, + var bubbleIconProps = { + localPosition: newBubbleIconLocalPosition }; - bubbleIconEntity = Entities.addEntity(props, "local"); - tablet.loadQMLSource(AVATAR_INPUTS_EDIT_QML_SOURCE); - } else { + + Entities.editEntity(micBarEntity, micBarProps); + Entities.editEntity(bubbleIconEntity, bubbleIconProps); + } else if (message.method === "setVisible") { + if (message.visible !== undefined) { + var props = { + visible: message.visible + }; + Entities.editEntity(micBarEntity, props); + Entities.editEntity(bubbleIconEntity, props); + } + } else if (message.method === "print") { + // prints the local position into the hifi log. + var micBarLocalPosition = Entities.getEntityProperties(micBarEntity).localPosition; + var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition; + console.log("mic bar local position is at " + JSON.stringify(micBarLocalPosition)); + console.log("bubble icon local position is at " + JSON.stringify(bubbleIconLocalPosition)); + } else if (message.method === "destroy") { + console.log("destroying"); Entities.deleteEntity(micBarEntity); Entities.deleteEntity(bubbleIconEntity); } }; - function setup() { - // button = tablet.addButton({ - // icon: "icons/tablet-icons/edit-i.svg", - // activeIcon: "icons/tablet-icons/edit-a.svg", - // text: buttonName - // }); - ui = new AppUi({ - buttonName: "AVBAR", - home: Script.resourcesPath() + "qml/hifi/EditAvatarInputsBar.qml", - onMessage: fromQml, - // normalButton: "icons/tablet-icons/avatar-i.svg", - // activeButton: "icons/tablet-icons/avatar-a.svg", - }); - button.clicked.connect(onClicked); + function createEntities(){ + // POSITIONS + var micBarLocalPosition = {x: (-(MIC_BAR_DIMENSIONS.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; + var bubbleIconLocalPosition = {x: (MIC_BAR_DIMENSIONS.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; + var props = { + type: "Web", + name: "AvatarInputsMicBarEntity", + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), + localPosition: micBarLocalPosition, + localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, micBarLocalPosition)), + sourceUrl: Script.resourcesPath() + "qml/hifi/audio/MicBarApplication.qml", + // cutoff alpha for detecting transparency + alpha: 0.98, + dimensions: MIC_BAR_DIMENSIONS, + drawInFront: true, + userData: { + grabbable: false + }, + }; + micBarEntity = Entities.addEntity(props, "local"); + var props = { + type: "Web", + name: "AvatarInputsBubbleIconEntity", + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), + localPosition: bubbleIconLocalPosition, + localRotation: Quat.cancelOutRollAndPitch(Quat.lookAtSimple(Camera.orientation, bubbleIconLocalPosition)), + sourceUrl: Script.resourcesPath() + "qml/BubbleIcon.qml", + // cutoff alpha for detecting transparency + alpha: 0.98, + dimensions: BUBBLE_ICON_DIMENSIONS, + drawInFront: true, + userData: { + grabbable: false + }, + }; + bubbleIconEntity = Entities.addEntity(props, "local"); + tablet.loadQMLSource(AVATAR_INPUTS_EDIT_QML_SOURCE); }; - - setup(); - - Script.scriptEnding.connect(function() { + function cleanup() { if (micBarEntity) { Entities.deleteEntity(micBarEntity); } if (bubbleIconEntity) { Entities.deleteEntity(bubbleIconEntity); } - tablet.removeButton(button); - }); + }; + + function setup() { + ui = new AppUi({ + buttonName: "AVBAR", + home: Script.resourcesPath() + "qml/hifi/EditAvatarInputsBar.qml", + onMessage: fromQml, + onOpened: createEntities, + onClosed: cleanup, + // normalButton: "icons/tablet-icons/avatar-i.svg", + // activeButton: "icons/tablet-icons/avatar-a.svg", + }); + }; + + setup(); + + Script.scriptEnding.connect(cleanup); }()); From 1f71a291a5fe968f6b35dd1e8accc1876044d4c9 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 21 Mar 2019 08:38:31 -0700 Subject: [PATCH 40/78] displaying offset values --- .../qml/hifi/EditAvatarInputsBar.qml | 8 +++--- scripts/system/createAvatarInputsBarEntity.js | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/EditAvatarInputsBar.qml b/interface/resources/qml/hifi/EditAvatarInputsBar.qml index bbf3652a92..b27b0c8db2 100644 --- a/interface/resources/qml/hifi/EditAvatarInputsBar.qml +++ b/interface/resources/qml/hifi/EditAvatarInputsBar.qml @@ -28,8 +28,6 @@ Rectangle { signal sendToScript(var message); function emitSendToScript(message) { - console.log("sending to script"); - console.log(JSON.stringify(message)); sendToScript(message); } @@ -57,7 +55,7 @@ Rectangle { left: parent.left leftMargin: 20 } - label: "X OFFSET" + label: "X OFFSET: " + value.toFixed(2); maximumValue: 1.0 minimumValue: -1.0 stepSize: 0.05 @@ -79,7 +77,7 @@ Rectangle { left: parent.left leftMargin: 20 } - label: "Y OFFSET" + label: "Y OFFSET: " + value.toFixed(2); maximumValue: 1.0 minimumValue: -1.0 stepSize: 0.05 @@ -101,7 +99,7 @@ Rectangle { left: parent.left leftMargin: 20 } - label: "Z OFFSET" + label: "Z OFFSET: " + value.toFixed(2); maximumValue: 0.0 minimumValue: -1.0 stepSize: 0.05 diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index 35c50416ed..500f8563fb 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -6,13 +6,17 @@ var ui; var onCreateAvatarInputsBarEntity = false; - var micBarEntity, bubbleIconEntity; + var micBarEntity = null; + var bubbleIconEntity = null; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml"; // QML NATURAL DIMENSIONS var MIC_BAR_DIMENSIONS = {x: 0.036, y: 0.048, z: 0.3}; var BUBBLE_ICON_DIMENSIONS = {x: 0.036, y: 0.036, z: 0.3}; + // ENTITY NAMES + var MIC_BAR_NAME = "AvatarInputsMicBarEntity"; + var BUBBLE_ICON_NAME = "AvatarInputsBubbleIconEntity"; // CONSTANTS var LOCAL_POSITION_X_OFFSET = -0.2; var LOCAL_POSITION_Y_OFFSET = -0.125; @@ -56,20 +60,19 @@ var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition; console.log("mic bar local position is at " + JSON.stringify(micBarLocalPosition)); console.log("bubble icon local position is at " + JSON.stringify(bubbleIconLocalPosition)); - } else if (message.method === "destroy") { - console.log("destroying"); - Entities.deleteEntity(micBarEntity); - Entities.deleteEntity(bubbleIconEntity); } }; - function createEntities(){ + function createEntities() { + if (micBarEntity != null && bubbleIconEntity != null) { + return; + } // POSITIONS var micBarLocalPosition = {x: (-(MIC_BAR_DIMENSIONS.x / 2)) + LOCAL_POSITION_X_OFFSET, y: LOCAL_POSITION_Y_OFFSET, z: LOCAL_POSITION_Z_OFFSET}; var bubbleIconLocalPosition = {x: (MIC_BAR_DIMENSIONS.x * 1.2 / 2) + LOCAL_POSITION_X_OFFSET, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + LOCAL_POSITION_Y_OFFSET), z: LOCAL_POSITION_Z_OFFSET}; var props = { type: "Web", - name: "AvatarInputsMicBarEntity", + name: MIC_BAR_NAME, parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), localPosition: micBarLocalPosition, @@ -86,7 +89,7 @@ micBarEntity = Entities.addEntity(props, "local"); var props = { type: "Web", - name: "AvatarInputsBubbleIconEntity", + name: BUBBLE_ICON_NAME, parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX"), localPosition: bubbleIconLocalPosition, @@ -118,9 +121,9 @@ home: Script.resourcesPath() + "qml/hifi/EditAvatarInputsBar.qml", onMessage: fromQml, onOpened: createEntities, - onClosed: cleanup, - // normalButton: "icons/tablet-icons/avatar-i.svg", - // activeButton: "icons/tablet-icons/avatar-a.svg", + // onClosed: cleanup, + normalButton: "icons/tablet-icons/edit-i.svg", + activeButton: "icons/tablet-icons/edit-a.svg", }); }; From 7b5deb692c7aff78675cb9a4239a1446b8026ed7 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 21 Mar 2019 09:57:31 -0700 Subject: [PATCH 41/78] fixing x value change --- scripts/system/createAvatarInputsBarEntity.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/system/createAvatarInputsBarEntity.js index 500f8563fb..deb0cfdd89 100644 --- a/scripts/system/createAvatarInputsBarEntity.js +++ b/scripts/system/createAvatarInputsBarEntity.js @@ -11,9 +11,11 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var AVATAR_INPUTS_EDIT_QML_SOURCE = "hifi/EditAvatarInputsBar.qml"; + // DPI + var ENTITY_DPI = 60.0; // QML NATURAL DIMENSIONS - var MIC_BAR_DIMENSIONS = {x: 0.036, y: 0.048, z: 0.3}; - var BUBBLE_ICON_DIMENSIONS = {x: 0.036, y: 0.036, z: 0.3}; + var MIC_BAR_DIMENSIONS = Vec3.multiply(30.0 / ENTITY_DPI, {x: 0.036, y: 0.048, z: 0.3}); + var BUBBLE_ICON_DIMENSIONS = Vec3.multiply(30.0 / ENTITY_DPI, {x: 0.036, y: 0.036, z: 0.3}); // ENTITY NAMES var MIC_BAR_NAME = "AvatarInputsMicBarEntity"; var BUBBLE_ICON_NAME = "AvatarInputsBubbleIconEntity"; @@ -28,8 +30,8 @@ var bubbleIconLocalPosition = Entities.getEntityProperties(bubbleIconEntity).localPosition; var newMicBarLocalPosition, newBubbleIconLocalPosition; if (message.x !== undefined) { - newMicBarLocalPosition = { x: -((MIC_BAR_DIMENSIONS.x) / 2) - message.x, y: micBarLocalPosition.y, z: micBarLocalPosition.z }; - newBubbleIconLocalPosition = { x: ((MIC_BAR_DIMENSIONS.x) * 1.2 / 2) - message.x, y: bubbleIconLocalPosition.y, z: bubbleIconLocalPosition.z }; + newMicBarLocalPosition = { x: -((MIC_BAR_DIMENSIONS.x) / 2) + message.x, y: micBarLocalPosition.y, z: micBarLocalPosition.z }; + newBubbleIconLocalPosition = { x: ((MIC_BAR_DIMENSIONS.x) * 1.2 / 2) + message.x, y: bubbleIconLocalPosition.y, z: bubbleIconLocalPosition.z }; } else if (message.y !== undefined) { newMicBarLocalPosition = { x: micBarLocalPosition.x, y: message.y, z: micBarLocalPosition.z }; newBubbleIconLocalPosition = { x: bubbleIconLocalPosition.x, y: ((MIC_BAR_DIMENSIONS.y - BUBBLE_ICON_DIMENSIONS.y) / 2 + message.y), z: bubbleIconLocalPosition.z }; @@ -81,6 +83,7 @@ // cutoff alpha for detecting transparency alpha: 0.98, dimensions: MIC_BAR_DIMENSIONS, + dpi: ENTITY_DPI, drawInFront: true, userData: { grabbable: false @@ -98,6 +101,7 @@ // cutoff alpha for detecting transparency alpha: 0.98, dimensions: BUBBLE_ICON_DIMENSIONS, + dpi: ENTITY_DPI, drawInFront: true, userData: { grabbable: false From 0edbf12fa373dfb415aa8ed0f0d45a009fd8409d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 22 Mar 2019 15:26:57 -0700 Subject: [PATCH 42/78] removing extra onLevelChanged --- .../qml/hifi/audio/MicBarApplication.qml | 10 -- interface/src/ui/PrivacyShield.cpp | 159 ------------------ 2 files changed, 169 deletions(-) delete mode 100644 interface/src/ui/PrivacyShield.cpp diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index f2839aee1a..bfac278ee4 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -53,16 +53,6 @@ Rectangle { micBar.opacity = rectOpacity; } - onLevelChanged: { - var rectOpacity = muted && (level >= userSpeakingLevel) ? 0.9 : 0.3; - if (pushToTalk && !pushingToTalk) { - rectOpacity = (level >= userSpeakingLevel) ? 0.9 : 0.7; - } else if (mouseArea.containsMouse && rectOpacity != 0.9) { - rectOpacity = 0.5; - } - micBar.opacity = rectOpacity; - } - color: "#00000000"; border { width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; diff --git a/interface/src/ui/PrivacyShield.cpp b/interface/src/ui/PrivacyShield.cpp deleted file mode 100644 index e8f61ff5bf..0000000000 --- a/interface/src/ui/PrivacyShield.cpp +++ /dev/null @@ -1,159 +0,0 @@ -// -// PrivacyShield.cpp -// interface/src/ui -// -// Created by Wayne Chen on 2/27/19. -// 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 -// - -#include "PrivacyShield.h" - -#include -#include -#include -#include -#include -#include - -#include "Application.h" -#include "PathUtils.h" -#include "GLMHelpers.h" - -const int PRIVACY_SHIELD_VISIBLE_DURATION_MS = 3000; -const int PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS = 750; -const int PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS = 15000; -const float PRIVACY_SHIELD_HEIGHT_SCALE = 0.15f; - -PrivacyShield::PrivacyShield() { - auto usersScriptingInterface = DependencyManager::get(); - //connect(usersScriptingInterface.data(), &UsersScriptingInterface::ignoreRadiusEnabledChanged, [this](bool enabled) { - // onPrivacyShieldToggled(enabled); - //}); - //connect(usersScriptingInterface.data(), &UsersScriptingInterface::enteredIgnoreRadius, this, &PrivacyShield::enteredIgnoreRadius); -} - -void PrivacyShield::createPrivacyShield() { - // Affects bubble height - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarScale = myAvatar->getTargetScale(); - auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - auto avatarWorldPosition = myAvatar->getWorldPosition(); - auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - EntityItemProperties properties; - properties.setName("Privacy-Shield"); - properties.setModelURL(PathUtils::resourcesUrl("assets/models/Bubble-v14.fbx").toString()); - properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - properties.setPosition(glm::vec3(avatarWorldPosition.x, - -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); - properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - properties.setVisible(false); - - _localPrivacyShieldID = DependencyManager::get()->addEntityInternal(properties, entity::HostType::LOCAL); - //_bubbleActivateSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl() + "assets/sounds/bubble.wav"); - - //onPrivacyShieldToggled(DependencyManager::get()->getIgnoreRadiusEnabled(), true); -} - -void PrivacyShield::destroyPrivacyShield() { - DependencyManager::get()->deleteEntity(_localPrivacyShieldID); -} - -void PrivacyShield::update(float deltaTime) { - if (_updateConnected) { - auto now = usecTimestampNow(); - auto delay = (now - _privacyShieldTimestamp); - auto privacyShieldAlpha = 1.0 - (delay / PRIVACY_SHIELD_VISIBLE_DURATION_MS); - if (privacyShieldAlpha > 0) { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarScale = myAvatar->getTargetScale(); - auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - auto avatarWorldPosition = myAvatar->getWorldPosition(); - auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - EntityItemProperties properties; - properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - properties.setRotation(avatarWorldOrientation * Quaternions::Y_180); - if (delay < PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS) { - properties.setPosition(glm::vec3(avatarWorldPosition.x, - (-((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * avatarScale * 2.0 + - avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setModelScale(glm::vec3(2.0, - ((1 - ((PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS - delay) / PRIVACY_SHIELD_RAISE_ANIMATION_DURATION_MS)) * - (0.5 * (avatarScale + 1.0))), 2.0)); - } else { - properties.setPosition(glm::vec3(avatarWorldPosition.x, avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - } - DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); - } - else { - hidePrivacyShield(); - if (_updateConnected) { - _updateConnected = false; - } - } - } -} - -void PrivacyShield::enteredIgnoreRadius() { - showPrivacyShield(); - DependencyManager::get()->privacyShieldActivated(); -} - -void PrivacyShield::onPrivacyShieldToggled(bool enabled, bool doNotLog) { - if (!doNotLog) { - DependencyManager::get()->privacyShieldToggled(enabled); - } - if (enabled) { - showPrivacyShield(); - } else { - hidePrivacyShield(); - if (_updateConnected) { - _updateConnected = false; - } - } -} - -void PrivacyShield::showPrivacyShield() { - auto now = usecTimestampNow(); - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarScale = myAvatar->getTargetScale(); - auto avatarSensorToWorldScale = myAvatar->getSensorToWorldScale(); - auto avatarWorldPosition = myAvatar->getWorldPosition(); - auto avatarWorldOrientation = myAvatar->getWorldOrientation(); - if (now - _lastPrivacyShieldSoundTimestamp >= PRIVACY_SHIELD_SOUND_RATE_LIMIT_MS) { - AudioInjectorOptions options; - options.position = avatarWorldPosition; - options.localOnly = true; - options.volume = 0.2f; - AudioInjector::playSoundAndDelete(_bubbleActivateSound, options); - _lastPrivacyShieldSoundTimestamp = now; - } - hidePrivacyShield(); - if (_updateConnected) { - _updateConnected = false; - } - - EntityItemProperties properties; - properties.setDimensions(glm::vec3(avatarSensorToWorldScale, 0.75 * avatarSensorToWorldScale, avatarSensorToWorldScale)); - properties.setPosition(glm::vec3(avatarWorldPosition.x, - -avatarScale * 2 + avatarWorldPosition.y + avatarScale * PRIVACY_SHIELD_HEIGHT_SCALE, avatarWorldPosition.z)); - properties.setModelScale(glm::vec3(2.0, 0.5 * (avatarScale + 1.0), 2.0)); - properties.setVisible(true); - - DependencyManager::get()->editEntity(_localPrivacyShieldID, properties); - - _privacyShieldTimestamp = now; - _updateConnected = true; -} - -void PrivacyShield::hidePrivacyShield() { - EntityTreePointer entityTree = qApp->getEntities()->getTree(); - EntityItemPointer privacyShieldEntity = entityTree->findEntityByEntityItemID(EntityItemID(_localPrivacyShieldID)); - if (privacyShieldEntity) { - privacyShieldEntity->setVisible(false); - } -} From 390ce9bb26951a825942e6589af8f0ef0e66d491 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 22 Mar 2019 15:34:06 -0700 Subject: [PATCH 43/78] wip for getting AvatarInputsBar to switch accordingly --- interface/resources/qml/AvatarInputsBar.qml | 21 ++++++++++++++----- interface/resources/qml/BubbleIcon.qml | 14 +++++++++++-- interface/resources/qml/hifi/audio/MicBar.qml | 15 ++++++------- .../qml/hifi/audio/MicBarApplication.qml | 15 ++++++------- .../resources/qml/hifi/tablet/TabletHome.qml | 2 +- interface/src/Application.cpp | 4 ++-- scripts/defaultScripts.js | 3 +-- .../createAvatarInputsBarEntity.js | 0 scripts/system/away.js | 4 ++-- 9 files changed, 50 insertions(+), 28 deletions(-) rename scripts/{system => developer}/createAvatarInputsBarEntity.js (100%) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index dfff103aa0..adeb74242d 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -18,20 +18,31 @@ Item { id: root; objectName: "AvatarInputsBar" property int modality: Qt.NonModal - readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled - width: audio.width; - height: audio.height; + readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled; + width: HMD.active ? audio.width : audioApplication.width; + height: HMD.active ? audio.height : audioApplication.height; x: 10; y: 5; readonly property bool shouldReposition: true; - HifiAudio.MicBarApplication { + HifiAudio.MicBar { id: audio; - visible: AvatarInputs.showAudioTools; + visible: AvatarInputs.showAudioTools && HMD.active; + standalone: true; + dragTarget: parent; + } + + HifiAudio.MicBarApplication { + id: audioApplication; + visible: AvatarInputs.showAudioTools && !HMD.active; + onVisibleChanged: { + console.log("visible changed: " + visible); + } standalone: true; dragTarget: parent; } BubbleIcon { dragTarget: parent + visible: !HMD.active; } } diff --git a/interface/resources/qml/BubbleIcon.qml b/interface/resources/qml/BubbleIcon.qml index f9c57697f0..1ad73f6179 100644 --- a/interface/resources/qml/BubbleIcon.qml +++ b/interface/resources/qml/BubbleIcon.qml @@ -19,8 +19,16 @@ Rectangle { width: bubbleIcon.width + 10 height: bubbleIcon.height + 10 radius: 5; - opacity: AvatarInputs.ignoreRadiusEnabled ? 0.7 : 0.3; property var dragTarget: null; + property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled; + + onIgnoreRadiusEnabledChanged: { + if (ignoreRadiusEnabled) { + bubbleRect.opacity = 0.7; + } else { + bubbleRect.opacity = 0.3; + } + } color: "#00000000"; border { @@ -58,15 +66,17 @@ Rectangle { } drag.target: dragTarget; onContainsMouseChanged: { + var rectOpacity = ignoreRadiusEnabled ? (containsMouse ? 0.9 : 0.7) : (containsMouse ? 0.5 : 0.3); if (containsMouse) { Tablet.playSound(TabletEnums.ButtonHover); } + bubbleRect.opacity = rectOpacity; } } Image { id: bubbleIcon source: "../icons/tablet-icons/bubble-i.svg"; - sourceSize: Qt.size(28, 28); + sourceSize: Qt.size(32, 32); smooth: true; anchors.top: parent.top anchors.topMargin: (parent.height - bubbleIcon.height) / 2 diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index fb52f8bc5e..71e3764826 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -16,6 +16,7 @@ import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + id: micBar HifiConstants { id: hifi; } readonly property var level: AudioScriptingInterface.inputLevel; @@ -72,7 +73,7 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { - if (AudioScriptingInterface.pushToTalk) { + if (pushToTalk) { return; } muted = !muted; @@ -98,7 +99,7 @@ Rectangle { readonly property string red: colors.muted; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: muted ? muted : unmuted; + readonly property string icon: micBar.muted ? muted : unmuted; } Item { @@ -122,7 +123,7 @@ Rectangle { readonly property string gatedIcon: "../../../icons/tablet-icons/mic-gate-i.svg"; id: image; - source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : + source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; width: 30; @@ -146,9 +147,9 @@ Rectangle { Item { id: status; - readonly property string color: muted ? colors.muted : colors.unmuted; + readonly property string color: colors.icon; - visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); + visible: (pushToTalk && !pushingToTalk) || muted; anchors { left: parent.left; @@ -177,7 +178,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; + width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } @@ -188,7 +189,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; + width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index bfac278ee4..f89ada6e49 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -126,7 +126,7 @@ Rectangle { Item { Image { id: image; - source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : + source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon; width: 29; height: 32; @@ -141,8 +141,7 @@ Rectangle { id: imageOverlay anchors { fill: image } source: image; - color: (pushToTalk && !pushingToTalk) ? ((level >= userSpeakingLevel) ? colors.mutedColor : - colors.unmutedColor) : colors.icon; + color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : colors.icon; } } } @@ -150,12 +149,12 @@ Rectangle { Item { id: status; - visible: (pushToTalk && !pushingToTalk) || (muted && (level >= userSpeakingLevel)); + visible: pushToTalk || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; top: icon.bottom; - topMargin: 5; + topMargin: 2; } width: parent.width; @@ -174,10 +173,10 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor; + color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor; font.bold: true - text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE"); + text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE"); size: 12; } } @@ -204,6 +203,7 @@ Rectangle { Rectangle { // mask id: mask; + visible: (!(pushToTalk && !pushingToTalk)) height: parent.height * level; width: parent.width; radius: 5; @@ -217,6 +217,7 @@ Rectangle { LinearGradient { anchors { fill: mask } + visible: (!(pushToTalk && !pushingToTalk)) source: mask start: Qt.point(0, 0); end: Qt.point(0, bar.height); diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index a1da69a44a..1a1e0a96ff 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -40,7 +40,7 @@ Item { } } - HifiAudio.MicBar { + HifiAudio.MicBarApplication { anchors { left: parent.left leftMargin: 30 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5b0c379c64..734eb7221b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -339,7 +339,7 @@ Setting::Handle maxOctreePacketsPerSecond{"maxOctreePPS", DEFAULT_MAX_OCTRE Setting::Handle loginDialogPoppedUp{"loginDialogPoppedUp", false}; static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml"); -static const QUrl MIC_BAR_ENTITY_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml"); +static const QUrl MIC_BAR_APPLICATION_QML = PathUtils::qmlUrl("hifi/audio/MicBarApplication.qml"); static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml"); static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action"; @@ -2396,7 +2396,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); auto rootItemLoadedFunctor = [webSurface, url, isTablet] { Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString() || url == AVATAR_INPUTS_BAR_QML.toString() || - url == BUBBLE_ICON_QML.toString() || url == MIC_BAR_ENTITY_QML.toString() ); + url == BUBBLE_ICON_QML.toString()); }; if (webSurface->getRootItem()) { rootItemLoadedFunctor(); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 0d9799a035..bd7e79dffc 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,8 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js", - "system/createAvatarInputsBarEntity.js" + "system/miniTablet.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/createAvatarInputsBarEntity.js b/scripts/developer/createAvatarInputsBarEntity.js similarity index 100% rename from scripts/system/createAvatarInputsBarEntity.js rename to scripts/developer/createAvatarInputsBarEntity.js diff --git a/scripts/system/away.js b/scripts/system/away.js index 2af43b2055..e45041139a 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -171,7 +171,7 @@ function goAway(fromStartup) { if (!previousBubbleState) { Users.toggleIgnoreRadius(); } - UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled()); + UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled()); UserActivityLogger.toggledAway(true); MyAvatar.isAway = true; } @@ -186,7 +186,7 @@ function goActive() { if (Users.getIgnoreRadiusEnabled() !== previousBubbleState) { Users.toggleIgnoreRadius(); - UserActivityLogger.bubbleToggled(Users.getIgnoreRadiusEnabled()); + UserActivityLogger.privacyShieldToggled(Users.getIgnoreRadiusEnabled()); } if (!Window.hasFocus()) { From 3ea45de7c73b87e34bea7707f9c36368a011cebc Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 22 Mar 2019 13:16:56 -0700 Subject: [PATCH 44/78] fixing qml bug in MicBar --- interface/resources/qml/AvatarInputsBar.qml | 35 ++++++++++++++----- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- interface/src/scripting/Audio.cpp | 3 ++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index adeb74242d..d975312aad 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -19,30 +19,49 @@ Item { objectName: "AvatarInputsBar" property int modality: Qt.NonModal readonly property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled; - width: HMD.active ? audio.width : audioApplication.width; - height: HMD.active ? audio.height : audioApplication.height; x: 10; y: 5; readonly property bool shouldReposition: true; + property bool hmdActive: HMD.active; + width: hmdActive ? audio.width : audioApplication.width; + height: hmdActive ? audio.height : audioApplication.height; + + onHmdActiveChanged: { + console.log("hmd active = " + hmdActive); + } + + Timer { + id: hmdActiveCheckTimer; + interval: 500; + repeat: true; + onTriggered: { + root.hmdActive = HMD.active; + } + + } HifiAudio.MicBar { id: audio; - visible: AvatarInputs.showAudioTools && HMD.active; + visible: AvatarInputs.showAudioTools && root.hmdActive; standalone: true; dragTarget: parent; } HifiAudio.MicBarApplication { id: audioApplication; - visible: AvatarInputs.showAudioTools && !HMD.active; - onVisibleChanged: { - console.log("visible changed: " + visible); - } + visible: AvatarInputs.showAudioTools && !root.hmdActive; standalone: true; dragTarget: parent; } + + Component.onCompleted: { + HMD.displayModeChanged.connect(function(isHmdMode) { + root.hmdActive = isHmdMode; + }); + } + BubbleIcon { dragTarget: parent - visible: !HMD.active; + visible: !root.hmdActive; } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 71e3764826..4b243e033a 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -19,9 +19,9 @@ Rectangle { id: micBar HifiConstants { id: hifi; } + property var muted: AudioScriptingInterface.muted; readonly property var level: AudioScriptingInterface.inputLevel; readonly property var clipping: AudioScriptingInterface.clipping; - readonly property var muted: AudioScriptingInterface.muted; readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 434688e474..0e0d13ae45 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -228,6 +228,9 @@ void Audio::loadData() { setMutedHMD(_hmdMutedSetting.get()); setPTTDesktop(_pttDesktopSetting.get()); setPTTHMD(_pttHMDSetting.get()); + + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false)); } bool Audio::getPTTHMD() const { From b98eda4674944f6d8eb210c1aad11aa743cf9716 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 22 Mar 2019 16:42:37 -0700 Subject: [PATCH 45/78] removing debug statemeng --- interface/resources/qml/AvatarInputsBar.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml index d975312aad..3f1f598991 100644 --- a/interface/resources/qml/AvatarInputsBar.qml +++ b/interface/resources/qml/AvatarInputsBar.qml @@ -26,10 +26,6 @@ Item { width: hmdActive ? audio.width : audioApplication.width; height: hmdActive ? audio.height : audioApplication.height; - onHmdActiveChanged: { - console.log("hmd active = " + hmdActive); - } - Timer { id: hmdActiveCheckTimer; interval: 500; From 7f5d9cbd40ae95518ef9ae5cfc6cb64e05698d71 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 22 Mar 2019 13:16:56 -0700 Subject: [PATCH 46/78] adding push to talk fix for loadData --- interface/resources/qml/hifi/audio/MicBar.qml | 19 +++++++++++-------- interface/src/scripting/Audio.cpp | 8 ++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f51da9c381..e1b3e05d43 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -18,7 +18,10 @@ import TabletScriptingInterface 1.0 Rectangle { HifiConstants { id: hifi; } + property var muted: AudioScriptingInterface.muted; readonly property var level: AudioScriptingInterface.inputLevel; + readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; + readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; property bool gated: false; Component.onCompleted: { @@ -67,10 +70,10 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { - if (AudioScriptingInterface.pushToTalk) { + if (pushToTalk) { return; } - AudioScriptingInterface.muted = !AudioScriptingInterface.muted; + muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; @@ -115,7 +118,7 @@ Rectangle { readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (pushToTalk && !pushingToTalk) ? pushToTalkIcon : muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -138,9 +141,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: muted ? colors.muted : colors.unmuted; - visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; + visible: (pushToTalk && !pushingToTalk) || muted; anchors { left: parent.left; @@ -159,7 +162,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -169,7 +172,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; + width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } @@ -180,7 +183,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; + width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b1b5077e60..0e0d13ae45 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -224,10 +224,10 @@ void Audio::saveData() { } void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); + setMutedDesktop(_desktopMutedSetting.get()); + setMutedHMD(_hmdMutedSetting.get()); + setPTTDesktop(_pttDesktopSetting.get()); + setPTTHMD(_pttHMDSetting.get()); auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted()), Q_ARG(bool, false)); From f551d49f4f0ab549fbb1a7c086012d12b4aed87e Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Mar 2019 11:31:05 -0700 Subject: [PATCH 47/78] adding signal for updating push to talk variables --- interface/resources/qml/hifi/audio/MicBar.qml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index e1b3e05d43..c154eabfaa 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -20,13 +20,20 @@ Rectangle { property var muted: AudioScriptingInterface.muted; readonly property var level: AudioScriptingInterface.inputLevel; - readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; - readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; + property var pushToTalk: AudioScriptingInterface.pushToTalk; + property var pushingToTalk: AudioScriptingInterface.pushingToTalk; property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + AudioScriptingInterface.pushToTalkChanged.connect(function() { + pushToTalk = AudioScriptingInterface.pushToTalk; + }); + AudioScriptingInterface.pushingToTalkChanged.connect(function() { + pushingToTalk = AudioScriptingInterface.pushingToTalk; + }); + } property bool standalone: false; From 59a420b1602d2bc8eb9a494601658993bc295e0c Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Mar 2019 12:24:13 -0700 Subject: [PATCH 48/78] adding signals + slots to when muted + display mode changes --- interface/resources/qml/hifi/audio/MicBar.qml | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index c154eabfaa..d161f12d70 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -27,6 +27,13 @@ Rectangle { Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + HMD.displayModeChanged.connect(function() { + muted = AudioScriptingInterface.muted; + pushToTalk = AudioScriptingInterface.pushToTalk; + }); + AudioScriptingInterface.mutedChanged.connect(function() { + muted = AudioScriptingInterface.muted; + }); AudioScriptingInterface.pushToTalkChanged.connect(function() { pushToTalk = AudioScriptingInterface.pushToTalk; }); @@ -94,16 +101,16 @@ Rectangle { QtObject { id: colors; - readonly property string unmuted: "#FFF"; - readonly property string muted: "#E2334D"; + readonly property string unmutedColor: "#FFF"; + readonly property string mutedColor: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; readonly property string yellow: "#C0C000"; - readonly property string red: colors.muted; + readonly property string red: colors.mutedColor; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: AudioScriptingInterface.muted ? muted : unmuted; + readonly property string icon: muted ? colors.mutedColor : unmutedColor; } Item { @@ -148,8 +155,6 @@ Rectangle { Item { id: status; - readonly property string color: muted ? colors.muted : colors.unmuted; - visible: (pushToTalk && !pushingToTalk) || muted; anchors { @@ -167,7 +172,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: parent.color; + color: colors.icon; text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE"); font.pointSize: 12; @@ -181,7 +186,7 @@ Rectangle { width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; - color: parent.color; + color: colors.icon; } Rectangle { @@ -192,7 +197,7 @@ Rectangle { width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; - color: parent.color; + color: colors.icon; } } From dc996c267f5af7a0203a1d9a4de84a630b68f2b7 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Mar 2019 12:24:13 -0700 Subject: [PATCH 49/78] adding signals + slots to when muted + display mode changes From 49ce30d536443dc989aa98b71f78d2293531d2a0 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Mar 2019 15:35:05 -0700 Subject: [PATCH 50/78] adding changes to design + truly separating muted/ptt --- .../qml/controlsUit/CheckBoxQQC2.qml | 3 +- .../resources/qml/controlsUit/Switch.qml | 5 +- interface/resources/qml/hifi/audio/Audio.qml | 57 ++++++++++++------- .../resources/qml/hifi/audio/InputPeak.qml | 20 +++---- .../qml/hifi/audio/LoopbackAudio.qml | 7 ++- .../qml/hifi/audio/PlaySampleSound.qml | 5 +- 6 files changed, 59 insertions(+), 38 deletions(-) diff --git a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml index 91d35ecd58..bd71025aa9 100644 --- a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml @@ -24,6 +24,7 @@ CheckBox { leftPadding: 0 property int colorScheme: hifi.colorSchemes.light property string color: hifi.colors.lightGrayText + property int fontSize: hifi.fontSizes.inputLabel readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property bool isRedCheck: false property bool isRound: false @@ -109,7 +110,7 @@ CheckBox { contentItem: Text { id: root - font.pixelSize: hifi.fontSizes.inputLabel + font.pixelSize: fontSize; font.family: "Raleway" font.weight: Font.DemiBold text: checkBox.text diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml index 4e1c21c456..422b08b4eb 100644 --- a/interface/resources/qml/controlsUit/Switch.qml +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -21,6 +21,7 @@ Item { property int switchWidth: 70; readonly property int switchRadius: height/2; property string labelTextOff: ""; + property int labelTextSize: hifi.fontSizes.inputLabel; property string labelGlyphOffText: ""; property int labelGlyphOffSize: 32; property string labelTextOn: ""; @@ -89,7 +90,7 @@ Item { RalewaySemiBold { id: labelOff; text: labelTextOff; - size: hifi.fontSizes.inputLabel; + size: labelTextSize; color: originalSwitch.checked ? hifi.colors.lightGrayText : "#FFFFFF"; anchors.top: parent.top; anchors.right: parent.right; @@ -130,7 +131,7 @@ Item { RalewaySemiBold { id: labelOn; text: labelTextOn; - size: hifi.fontSizes.inputLabel; + size: labelTextSize; color: originalSwitch.checked ? "#FFFFFF" : hifi.colors.lightGrayText; anchors.top: parent.top; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index cd0f290da4..79222ea792 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -31,6 +31,8 @@ Rectangle { property string title: "Audio Settings" property int switchHeight: 16 property int switchWidth: 40 + property bool pushToTalk: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + property bool muted: (bar.currentIndex === 0) ? AudioScriptingInterface.desktopMuted : AudioScriptingInterface.hmdMuted; readonly property real verticalScrollWidth: 10 readonly property real verticalScrollShaft: 8 signal sendToScript(var message); @@ -44,7 +46,7 @@ Rectangle { property bool isVR: AudioScriptingInterface.context === "VR" - property real rightMostInputLevelPos: 440 + property real rightMostInputLevelPos: root.width //placeholder for control sizes and paddings //recalculates dynamically in case of UI size is changed QtObject { @@ -92,7 +94,9 @@ Rectangle { } } - Component.onCompleted: enablePeakValues(); + Component.onCompleted: { + enablePeakValues(); + } Flickable { id: flickView; @@ -167,15 +171,25 @@ Rectangle { height: root.switchHeight; switchWidth: root.switchWidth; labelTextOn: "Mute microphone"; + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; - checked: AudioScriptingInterface.muted; + checked: muted; onClicked: { - if (AudioScriptingInterface.pushToTalk && !checked) { + if (pushToTalk && !checked) { // disable push to talk if unmuting - AudioScriptingInterface.pushToTalk = false; + if ((bar.currentIndex === 0)) { + AudioScriptingInterface.pushToTalkDesktop = false; + } + else { + AudioScriptingInterface.pushToTalkHMD = false; + } + } + if ((bar.currentIndex === 0)) { + AudioScriptingInterface.desktopMuted = checked; + } + else { + AudioScriptingInterface.hmdMuted = checked; } - AudioScriptingInterface.muted = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } } @@ -187,6 +201,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: "Noise Reduction"; + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.noiseReduction; onCheckedChanged: { @@ -203,6 +218,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: qsTr("Push To Talk (T)"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; onCheckedChanged: { @@ -211,13 +227,6 @@ Rectangle { } else { AudioScriptingInterface.pushToTalkHMD = checked; } - checked = Qt.binding(function() { - if (bar.currentIndex === 0) { - return AudioScriptingInterface.pushToTalkDesktop; - } else { - return AudioScriptingInterface.pushToTalkHMD; - } - }); // restore binding } } } @@ -235,6 +244,7 @@ Rectangle { anchors.top: parent.top anchors.left: parent.left labelTextOn: qsTr("Warn when muted"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.warnWhenMuted; onClicked: { @@ -252,6 +262,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: qsTr("Audio Level Meter"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AvatarInputs.showAudioTools; onCheckedChanged: { @@ -268,6 +279,7 @@ Rectangle { anchors.topMargin: 24 anchors.left: parent.left labelTextOn: qsTr("Stereo input"); + labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.isStereoInput; onCheckedChanged: { @@ -281,6 +293,7 @@ Rectangle { Item { id: pttTextContainer + visible: pushToTalk; anchors.top: switchesContainer.bottom; anchors.topMargin: 10; anchors.left: parent.left; @@ -303,7 +316,7 @@ Rectangle { Separator { id: secondSeparator; - anchors.top: pttTextContainer.bottom; + anchors.top: pttTextContainer.visible ? pttTextContainer.bottom : switchesContainer.bottom; anchors.topMargin: 10; } @@ -330,7 +343,7 @@ Rectangle { width: margins.sizeText + margins.sizeLevel; anchors.left: parent.left; anchors.leftMargin: margins.sizeCheckBox; - size: 16; + size: 22; color: hifi.colors.white; text: qsTr("Choose input device"); } @@ -338,7 +351,7 @@ Rectangle { ListView { id: inputView; - width: parent.width - margins.paddings*2; + width: rightMostInputLevelPos; anchors.top: inputDeviceHeader.bottom; anchors.topMargin: 10; x: margins.paddings @@ -347,7 +360,7 @@ Rectangle { clip: true; model: AudioScriptingInterface.devices.input; delegate: Item { - width: rightMostInputLevelPos + width: rightMostInputLevelPos - margins.paddings*2 height: margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox : checkBoxInput.implicitHeight @@ -363,6 +376,7 @@ Rectangle { boxSize: margins.sizeCheckBox / 2 isRound: true text: devicename + fontSize: 16; onPressed: { if (!checked) { stereoInput.checked = false; @@ -395,7 +409,7 @@ Rectangle { Separator { id: thirdSeparator; - anchors.top: loopbackAudio.bottom; + anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputView.bottom; anchors.topMargin: 10; } @@ -422,7 +436,7 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: margins.sizeCheckBox anchors.verticalCenter: parent.verticalCenter; - size: 16; + size: 22; color: hifi.colors.white; text: qsTr("Choose output device"); } @@ -452,6 +466,7 @@ Rectangle { checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; checkable: !checked text: devicename + fontSize: 16 onPressed: { if (!checked) { AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1); @@ -514,7 +529,7 @@ Rectangle { RalewayRegular { // The slider for my card is special, it controls the master gain id: gainSliderText; - text: "Avatar volume"; + text: "People volume"; size: 16; anchors.left: parent.left; color: hifi.colors.white; diff --git a/interface/resources/qml/hifi/audio/InputPeak.qml b/interface/resources/qml/hifi/audio/InputPeak.qml index 00f7e63528..d8b166cee4 100644 --- a/interface/resources/qml/hifi/audio/InputPeak.qml +++ b/interface/resources/qml/hifi/audio/InputPeak.qml @@ -12,24 +12,26 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -Rectangle { +Item { property var peak; width: 70; height: 8; - color: "transparent"; - - Item { + QtObject { id: colors; + readonly property string unmuted: "#FFF"; readonly property string muted: "#E2334D"; readonly property string gutter: "#575757"; readonly property string greenStart: "#39A38F"; readonly property string greenEnd: "#1FC6A6"; + readonly property string yellow: "#C0C000"; readonly property string red: colors.muted; + readonly property string fill: "#55000000"; } + Text { id: status; @@ -79,23 +81,19 @@ Rectangle { anchors { fill: mask } source: mask start: Qt.point(0, 0); - end: Qt.point(70, 0); + end: Qt.point(bar.width, 0); gradient: Gradient { GradientStop { position: 0; color: colors.greenStart; } GradientStop { - position: 0.8; + position: 0.5; color: colors.greenEnd; } - GradientStop { - position: 0.801; - color: colors.red; - } GradientStop { position: 1; - color: colors.red; + color: colors.yellow; } } } diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index 8ec0ffc496..b668568035 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -44,8 +44,11 @@ RowLayout { } HifiControlsUit.Button { - text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE"); + text: audioLoopedBack ? qsTr("STOP TESTING VOICE") : qsTr("TEST YOUR VOICE"); color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue; + fontSize: 15; + width: 200; + height: 32; onClicked: { if (audioLoopedBack) { loopbackTimer.stop(); @@ -59,7 +62,7 @@ RowLayout { RalewayRegular { Layout.leftMargin: 2; - size: 14; + size: 18; color: "white"; font.italic: true text: audioLoopedBack ? qsTr("Speak in your input") : ""; diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index b9d9727dab..8565512837 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -59,11 +59,14 @@ RowLayout { text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND"); color: isPlaying ? hifi.buttons.red : hifi.buttons.blue; onClicked: isPlaying ? stopSound() : playSound(); + fontSize: 15; + width: 200; + height: 32; } RalewayRegular { Layout.leftMargin: 2; - size: 14; + size: 18; color: "white"; font.italic: true text: isPlaying ? qsTr("Listen to your output") : ""; From 6aebd000d6be1a364d26725262d48ab121be7798 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Mar 2019 15:41:03 -0700 Subject: [PATCH 51/78] changing api to match verbiage of overall code --- interface/resources/qml/hifi/audio/Audio.qml | 6 ++--- interface/src/scripting/Audio.cpp | 24 ++++++++++---------- interface/src/scripting/Audio.h | 21 +++++++++-------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 79222ea792..3266f3ef46 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -32,7 +32,7 @@ Rectangle { property int switchHeight: 16 property int switchWidth: 40 property bool pushToTalk: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; - property bool muted: (bar.currentIndex === 0) ? AudioScriptingInterface.desktopMuted : AudioScriptingInterface.hmdMuted; + property bool muted: (bar.currentIndex === 0) ? AudioScriptingInterface.mutedDesktop : AudioScriptingInterface.mutedHMD; readonly property real verticalScrollWidth: 10 readonly property real verticalScrollShaft: 8 signal sendToScript(var message); @@ -185,10 +185,10 @@ Rectangle { } } if ((bar.currentIndex === 0)) { - AudioScriptingInterface.desktopMuted = checked; + AudioScriptingInterface.mutedDesktop = checked; } else { - AudioScriptingInterface.hmdMuted = checked; + AudioScriptingInterface.mutedHMD = checked; } } } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 0e0d13ae45..df88538724 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -88,44 +88,44 @@ void Audio::setMuted(bool isMuted) { void Audio::setMutedDesktop(bool isMuted) { bool changed = false; withWriteLock([&] { - if (_desktopMuted != isMuted) { + if (_mutedDesktop != isMuted) { changed = true; - _desktopMuted = isMuted; + _mutedDesktop = isMuted; auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); } }); if (changed) { emit mutedChanged(isMuted); - emit desktopMutedChanged(isMuted); + emit mutedDesktopChanged(isMuted); } } bool Audio::getMutedDesktop() const { return resultWithReadLock([&] { - return _desktopMuted; + return _mutedDesktop; }); } void Audio::setMutedHMD(bool isMuted) { bool changed = false; withWriteLock([&] { - if (_hmdMuted != isMuted) { + if (_mutedHMD != isMuted) { changed = true; - _hmdMuted = isMuted; + _mutedHMD = isMuted; auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); } }); if (changed) { emit mutedChanged(isMuted); - emit hmdMutedChanged(isMuted); + emit mutedHMDChanged(isMuted); } } bool Audio::getMutedHMD() const { return resultWithReadLock([&] { - return _hmdMuted; + return _mutedHMD; }); } @@ -217,15 +217,15 @@ void Audio::setPTTHMD(bool enabled) { } void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); + _mutedDesktopSetting.set(getMutedDesktop()); + _mutedHMDSetting.set(getMutedHMD()); _pttDesktopSetting.set(getPTTDesktop()); _pttHMDSetting.set(getPTTHMD()); } void Audio::loadData() { - setMutedDesktop(_desktopMutedSetting.get()); - setMutedHMD(_hmdMutedSetting.get()); + setMutedDesktop(_mutedDesktopSetting.get()); + setMutedHMD(_mutedHMDSetting.get()); setPTTDesktop(_pttDesktopSetting.get()); setPTTHMD(_pttHMDSetting.get()); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 9ee230fc29..dba3af0730 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -41,6 +41,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @hifi-assignment-client * * @property {boolean} muted - true if the audio input is muted, otherwise false. + * @property {boolean} mutedDesktop - true if the audio input is muted, otherwise false. * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just * above the noise floor. @@ -68,8 +69,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) - Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) - Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool mutedDesktop READ getMutedDesktop WRITE setMutedDesktop NOTIFY mutedDesktopChanged) + Q_PROPERTY(bool mutedHMD READ getMutedHMD WRITE setMutedHMD NOTIFY mutedHMDChanged) Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) @@ -227,19 +228,19 @@ signals: /**jsdoc * Triggered when desktop audio input is muted or unmuted. - * @function Audio.desktopMutedChanged + * @function Audio.mutedDesktopChanged * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. * @returns {Signal} */ - void desktopMutedChanged(bool isMuted); + void mutedDesktopChanged(bool isMuted); /**jsdoc * Triggered when HMD audio input is muted or unmuted. - * @function Audio.hmdMutedChanged + * @function Audio.mutedHMDChanged * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. * @returns {Signal} */ - void hmdMutedChanged(bool isMuted); + void mutedHMDChanged(bool isMuted); /** * Triggered when Push-to-Talk has been enabled or disabled. @@ -356,12 +357,12 @@ private: bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; - Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; - Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _mutedDesktopSetting{ QStringList { Audio::AUDIO, "mutedDesktop" }, true }; + Setting::Handle _mutedHMD{ QStringList { Audio::AUDIO, "mutedHMD" }, true }; Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; - bool _desktopMuted{ true }; - bool _hmdMuted{ false }; + bool _mutedDesktop{ true }; + bool _mutedHMD{ false }; bool _pttDesktop{ false }; bool _pttHMD{ false }; bool _pushingToTalk{ false }; From c54e8f55693be7175839700c6e00d04b368bbc77 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 25 Mar 2019 17:32:03 -0700 Subject: [PATCH 52/78] showing ptt text always + hmd mode switch fix --- interface/resources/qml/BubbleIcon.qml | 10 +++++++++- interface/resources/qml/hifi/audio/Audio.qml | 5 ++--- interface/resources/qml/hifi/audio/MicBar.qml | 15 +++++++++------ .../qml/hifi/audio/MicBarApplication.qml | 13 +++++++++---- scripts/system/audio.js | 2 ++ 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/interface/resources/qml/BubbleIcon.qml b/interface/resources/qml/BubbleIcon.qml index 1ad73f6179..430eb19860 100644 --- a/interface/resources/qml/BubbleIcon.qml +++ b/interface/resources/qml/BubbleIcon.qml @@ -22,7 +22,7 @@ Rectangle { property var dragTarget: null; property bool ignoreRadiusEnabled: AvatarInputs.ignoreRadiusEnabled; - onIgnoreRadiusEnabledChanged: { + function updateOpacity() { if (ignoreRadiusEnabled) { bubbleRect.opacity = 0.7; } else { @@ -30,6 +30,14 @@ Rectangle { } } + Component.onCompleted: { + updateOpacity(); + } + + onIgnoreRadiusEnabledChanged: { + updateOpacity(); + } + color: "#00000000"; border { width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 3266f3ef46..015a9542e9 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -177,14 +177,14 @@ Rectangle { onClicked: { if (pushToTalk && !checked) { // disable push to talk if unmuting - if ((bar.currentIndex === 0)) { + if (bar.currentIndex === 0) { AudioScriptingInterface.pushToTalkDesktop = false; } else { AudioScriptingInterface.pushToTalkHMD = false; } } - if ((bar.currentIndex === 0)) { + if (bar.currentIndex === 0) { AudioScriptingInterface.mutedDesktop = checked; } else { @@ -293,7 +293,6 @@ Rectangle { Item { id: pttTextContainer - visible: pushToTalk; anchors.top: switchesContainer.bottom; anchors.topMargin: 10; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 4b243e033a..b6254b168c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -22,14 +22,18 @@ Rectangle { property var muted: AudioScriptingInterface.muted; readonly property var level: AudioScriptingInterface.inputLevel; readonly property var clipping: AudioScriptingInterface.clipping; - readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; - readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; + property var pushToTalk: AudioScriptingInterface.pushToTalk; + property var pushingToTalk: AudioScriptingInterface.pushingToTalk; readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + HMD.displayModeChanged.connect(function() { + muted = AudioScriptingInterface.muted; + pushToTalk = AudioScriptingInterface.pushToTalk; + }); } property bool standalone: false; @@ -147,7 +151,6 @@ Rectangle { Item { id: status; - readonly property string color: colors.icon; visible: (pushToTalk && !pushingToTalk) || muted; @@ -166,7 +169,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: parent.color; + color: colors.icon; text: (pushToTalk && !pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (muted ? "MUTED" : "MUTE"); font.pointSize: 12; @@ -180,7 +183,7 @@ Rectangle { width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; - color: parent.color; + color: colors.icon; } Rectangle { @@ -191,7 +194,7 @@ Rectangle { width: pushToTalk && !pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; - color: parent.color; + color: colors.icon; } } diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index f89ada6e49..509517063d 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -19,14 +19,18 @@ Rectangle { id: micBar; readonly property var level: AudioScriptingInterface.inputLevel; readonly property var clipping: AudioScriptingInterface.clipping; - readonly property var muted: AudioScriptingInterface.muted; - readonly property var pushToTalk: AudioScriptingInterface.pushToTalk; - readonly property var pushingToTalk: AudioScriptingInterface.pushingToTalk; + property var muted: AudioScriptingInterface.muted; + property var pushToTalk: AudioScriptingInterface.pushToTalk; + property var pushingToTalk: AudioScriptingInterface.pushingToTalk; readonly property var userSpeakingLevel: 0.4; property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); + HMD.displayModeChanged.connect(function() { + muted = AudioScriptingInterface.muted; + pushToTalk = AudioScriptingInterface.pushToTalk; + }); } readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; @@ -86,8 +90,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { - AudioScriptingInterface.muted = !AudioScriptingInterface.muted; + AudioScriptingInterface.muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); + muted = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } drag.target: dragTarget; onContainsMouseChanged: { diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 19ed3faef2..a161b40ffd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -75,6 +75,7 @@ button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); Audio.pushToTalkChanged.connect(onMuteToggled); +HMD.displayModeChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -84,6 +85,7 @@ Script.scriptEnding.connect(function () { tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); Audio.pushToTalkChanged.disconnect(onMuteToggled); + HMD.displayModeChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 060932ad4bcd609e4c434fcc18ee77d056b06bd7 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 26 Mar 2019 09:13:10 -0700 Subject: [PATCH 53/78] fixing typo --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index dba3af0730..90687e220e 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -358,7 +358,7 @@ private: AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; Setting::Handle _mutedDesktopSetting{ QStringList { Audio::AUDIO, "mutedDesktop" }, true }; - Setting::Handle _mutedHMD{ QStringList { Audio::AUDIO, "mutedHMD" }, true }; + Setting::Handle _mutedHMDSetting{ QStringList { Audio::AUDIO, "mutedHMD" }, true }; Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; bool _mutedDesktop{ true }; From 40d424a01d686077a114971a6704942bfe8de1b5 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 27 Mar 2019 16:42:34 -0700 Subject: [PATCH 54/78] avatar fading --- interface/src/avatar/.#AvatarManager.cpp | 1 + interface/src/avatar/AvatarManager.cpp | 26 ++++++++---- interface/src/avatar/AvatarManager.h | 7 ++-- interface/src/avatar/OtherAvatar.cpp | 1 + .../src/avatars-renderer/Avatar.h | 1 + libraries/avatars/src/AvatarData.cpp | 1 + libraries/render/src/render/Scene.cpp | 42 +++++++++++++++++-- libraries/render/src/render/Scene.h | 12 +++++- libraries/render/src/render/Transition.h | 2 +- 9 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 interface/src/avatar/.#AvatarManager.cpp diff --git a/interface/src/avatar/.#AvatarManager.cpp b/interface/src/avatar/.#AvatarManager.cpp new file mode 100644 index 0000000000..b55ef44c07 --- /dev/null +++ b/interface/src/avatar/.#AvatarManager.cpp @@ -0,0 +1 @@ +Dante@DESKTOP-TUOA3HH.30568:1553633650 \ No newline at end of file diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 69f7054953..ea8cdc8105 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -210,7 +210,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { { // lock the hash for read to check the size QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFadeOut.isEmpty()) { + if (_avatarHash.size() < 2 && _avatarsToFadeOut.empty()) { return; } } @@ -386,8 +386,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { _numAvatarsNotUpdated = numAvatarsNotUpdated; _numHeroAvatarsUpdated = numHerosUpdated; - simulateAvatarFades(deltaTime); - + removeFadedAvatars(); _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; } @@ -400,18 +399,17 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen } } -void AvatarManager::simulateAvatarFades(float deltaTime) { +void AvatarManager::removeFadedAvatars() { if (_avatarsToFadeOut.empty()) { return; } QReadLocker locker(&_hashLock); - QVector::iterator avatarItr = _avatarsToFadeOut.begin(); + auto avatarItr = _avatarsToFadeOut.begin(); const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; while (avatarItr != _avatarsToFadeOut.end()) { auto avatar = std::static_pointer_cast(*avatarItr); - avatar->updateFadingStatus(); if (!avatar->isFading()) { // fading to zero is such a rare event we push a unique transaction for each if (avatar->isInScene()) { @@ -452,7 +450,6 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact transaction.objectsToRemove.push_back(mState); } avatar->resetDetailedMotionStates(); - } else { if (avatar->getDetailedMotionStates().size() == 0) { avatar->createDetailedMotionStates(avatar); @@ -541,8 +538,21 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar // remove from node sets, if present DependencyManager::get()->removeFromIgnoreMuteSets(avatar->getSessionUUID()); DependencyManager::get()->avatarDisconnected(avatar->getSessionUUID()); - avatar->fadeOut(qApp->getMain3DScene(), removalReason); + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + avatar->fadeOut(scene, removalReason); + + AvatarData* avatarData = removedAvatar.get(); + transaction.transitionFinishedOperator(avatar->getRenderItemID(), [avatarDataWeakPtr]() { + auto avatarDataPtr = avatarDataWeakPtr.lock(); + + if (avatarDataPtr) { + auto avatar = std::static_pointer_cast(avatarDataPtr); + avatar->setIsFading(false); + } + }); } + _avatarsToFadeOut.push_back(removedAvatar); } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 0468fbd809..9dde3a11fb 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -220,10 +220,10 @@ private: explicit AvatarManager(QObject* parent = 0); explicit AvatarManager(const AvatarManager& other); - void simulateAvatarFades(float deltaTime); - AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override; + void removeFadedAvatars(); + // called only from the AvatarHashMap thread - cannot be called while this thread holds the // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree // frequently grabs a read lock on the hash to get a given avatar by ID @@ -231,8 +231,7 @@ private: KillAvatarReason removalReason = KillAvatarReason::NoReason) override; void handleTransitAnimations(AvatarTransit::Status status); - QVector _avatarsToFadeOut; - + std::vector _avatarsToFadeOut; using SetOfOtherAvatars = std::set; SetOfOtherAvatars _avatarsToChangeInPhysics; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 11eb6542c4..22ddea14c6 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -50,6 +50,7 @@ OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { } OtherAvatar::~OtherAvatar() { + qDebug() << "-------->"; removeOrb(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 6026367440..1eb760b857 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -463,6 +463,7 @@ public: void fadeIn(render::ScenePointer scene); void fadeOut(render::ScenePointer scene, KillAvatarReason reason); bool isFading() const { return _isFading; } + void setIsFading(bool isFading) { _isFading = isFading; } void updateFadingStatus(); // JSDoc is in AvatarData.h. diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 26407c3564..ee701020b5 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -132,6 +132,7 @@ AvatarData::AvatarData() : } AvatarData::~AvatarData() { + qDebug() << "AvatarData::~AvatarData()"; delete _headData; } diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 1850261c99..d3bcfb1f95 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -47,6 +47,10 @@ void Transaction::queryTransitionOnItem(ItemID id, TransitionQueryFunc func) { _queriedTransitions.emplace_back(id, func); } +void Transaction::transitionFinishedOperator(ItemID id, TransitionFinishedFunc func) { + _transitionFinishedOperators.emplace_back(id, func); +} + void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) { _updatedItems.emplace_back(id, functor); } @@ -75,6 +79,7 @@ void Transaction::reserve(const std::vector& transactionContainer) size_t addedTransitionsCount = 0; size_t queriedTransitionsCount = 0; size_t reAppliedTransitionsCount = 0; + size_t transitionFinishedOperatorsCount = 0; size_t highlightResetsCount = 0; size_t highlightRemovesCount = 0; size_t highlightQueriesCount = 0; @@ -85,6 +90,7 @@ void Transaction::reserve(const std::vector& transactionContainer) updatedItemsCount += transaction._updatedItems.size(); resetSelectionsCount += transaction._resetSelections.size(); addedTransitionsCount += transaction._addedTransitions.size(); + transitionFinishedOperatorsCount += transaction._transitionFinishedOperators.size(); queriedTransitionsCount += transaction._queriedTransitions.size(); reAppliedTransitionsCount += transaction._reAppliedTransitions.size(); highlightResetsCount += transaction._highlightResets.size(); @@ -99,6 +105,7 @@ void Transaction::reserve(const std::vector& transactionContainer) _addedTransitions.reserve(addedTransitionsCount); _queriedTransitions.reserve(queriedTransitionsCount); _reAppliedTransitions.reserve(reAppliedTransitionsCount); + _transitionFinishedOperators.reserve(transitionFinishedOperatorsCount); _highlightResets.reserve(highlightResetsCount); _highlightRemoves.reserve(highlightRemovesCount); _highlightQueries.reserve(highlightQueriesCount); @@ -142,6 +149,7 @@ void Transaction::merge(Transaction&& transaction) { moveElements(_resetSelections, transaction._resetSelections); moveElements(_addedTransitions, transaction._addedTransitions); moveElements(_queriedTransitions, transaction._queriedTransitions); + moveElements(_transitionFinishedOperators, transaction._transitionFinishedOperators); moveElements(_reAppliedTransitions, transaction._reAppliedTransitions); moveElements(_highlightResets, transaction._highlightResets); moveElements(_highlightRemoves, transaction._highlightRemoves); @@ -156,6 +164,7 @@ void Transaction::merge(const Transaction& transaction) { copyElements(_addedTransitions, transaction._addedTransitions); copyElements(_queriedTransitions, transaction._queriedTransitions); copyElements(_reAppliedTransitions, transaction._reAppliedTransitions); + copyElements(_transitionFinishedOperators, transaction._transitionFinishedOperators); copyElements(_highlightResets, transaction._highlightResets); copyElements(_highlightRemoves, transaction._highlightRemoves); copyElements(_highlightQueries, transaction._highlightQueries); @@ -168,6 +177,7 @@ void Transaction::clear() { _resetSelections.clear(); _addedTransitions.clear(); _queriedTransitions.clear(); + _transitionFinishedOperators.clear(); _reAppliedTransitions.clear(); _highlightResets.clear(); _highlightRemoves.clear(); @@ -261,6 +271,10 @@ void Scene::processTransactionFrame(const Transaction& transaction) { // Update the numItemsAtomic counter AFTER the reset changes went through _numAllocatedItems.exchange(maxID); + // reset transition finished operator + + resetTransitionFinishedOperator(transaction._transitionFinishedOperators); + // updates updateItems(transaction._updatedItems); @@ -440,6 +454,18 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti } } +void Scene::resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& transactions) { + for (auto& finishedOperator : transactions) { + auto itemId = std::get<0>(finishedOperator); + const auto& item = _items[itemId]; + auto func = std::get<1>(finishedOperator); + + if (item.exist() && func != nullptr) { + _transitionFinishedOperatorMap[itemId] = func; + } + } +} + void Scene::resetHighlights(const Transaction::HighlightResets& transactions) { auto outlineStage = getStage(HighlightStage::getName()); if (outlineStage) { @@ -528,8 +554,18 @@ void Scene::resetItemTransition(ItemID itemId) { auto& item = _items[itemId]; if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { auto transitionStage = getStage(TransitionStage::getName()); - transitionStage->removeTransition(item.getTransitionId()); - setItemTransition(itemId, render::TransitionStage::INVALID_INDEX); + auto transitionItemId = transitionStage->getTransition(item.getTransitionId()).itemId; + + if (transitionItemId == itemId) { + auto transitionFinishedOperator = _transitionFinishedOperatorMap[transitionItemId]; + + if (transitionFinishedOperator) { + transitionFinishedOperator(); + _transitionFinishedOperatorMap[transitionItemId] = nullptr; + } + transitionStage->removeTransition(item.getTransitionId()); + setItemTransition(itemId, render::TransitionStage::INVALID_INDEX); + } } } @@ -587,4 +623,4 @@ void Scene::resetStage(const Stage::Name& name, const StagePointer& stage) { } else { (*found).second = stage; } -} \ No newline at end of file +} diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index f00c74775d..c8eafcb696 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -32,12 +32,14 @@ class Scene; // These changes must be expressed through the corresponding command from the Transaction // THe Transaction is then queued on the Scene so all the pending transactions can be consolidated and processed at the time // of updating the scene before it s rendered. -// +// + class Transaction { friend class Scene; public: typedef std::function TransitionQueryFunc; + typedef std::function TransitionFinishedFunc; typedef std::function SelectionHighlightQueryFunc; Transaction() {} @@ -52,6 +54,7 @@ public: void removeTransitionFromItem(ItemID id); void reApplyTransitionToItem(ItemID id); void queryTransitionOnItem(ItemID id, TransitionQueryFunc func); + void transitionFinishedOperator(ItemID id, TransitionFinishedFunc func); template void updateItem(ItemID id, std::function func) { updateItem(id, std::make_shared>(func)); @@ -84,6 +87,7 @@ protected: using Update = std::tuple; using TransitionAdd = std::tuple; using TransitionQuery = std::tuple; + using TransitionFinishedOperator = std::tuple; using TransitionReApply = ItemID; using SelectionReset = Selection; using HighlightReset = std::tuple; @@ -95,6 +99,7 @@ protected: using Updates = std::vector; using TransitionAdds = std::vector; using TransitionQueries = std::vector; + using TransitionFinishedOperators = std::vector; using TransitionReApplies = std::vector; using SelectionResets = std::vector; using HighlightResets = std::vector; @@ -107,6 +112,7 @@ protected: TransitionAdds _addedTransitions; TransitionQueries _queriedTransitions; TransitionReApplies _reAppliedTransitions; + TransitionFinishedOperators _transitionFinishedOperators; SelectionResets _resetSelections; HighlightResets _highlightResets; HighlightRemoves _highlightRemoves; @@ -208,6 +214,7 @@ protected: ItemIDSet _masterNonspatialSet; void resetItems(const Transaction::Resets& transactions); + void resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& transactions); void removeItems(const Transaction::Removes& transactions); void updateItems(const Transaction::Updates& transactions); void transitionItems(const Transaction::TransitionAdds& transactions); @@ -223,6 +230,9 @@ protected: mutable std::mutex _selectionsMutex; // mutable so it can be used in the thread safe getSelection const method SelectionMap _selections; + mutable std::mutex _transitionFinishedOperatorMapMutex; + std::unordered_map _transitionFinishedOperatorMap; + void resetSelections(const Transaction::SelectionResets& transactions); // More actions coming to selections soon: // void removeFromSelection(const Selection& selection); diff --git a/libraries/render/src/render/Transition.h b/libraries/render/src/render/Transition.h index 30bda8aa2a..eca41e9d6c 100644 --- a/libraries/render/src/render/Transition.h +++ b/libraries/render/src/render/Transition.h @@ -50,4 +50,4 @@ namespace render { typedef std::vector TransitionTypes; } -#endif // hifi_render_Transition_h \ No newline at end of file +#endif // hifi_render_Transition_h From d9b522d10cb55025e6f8927f72d31f94690b8e96 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Wed, 27 Mar 2019 16:43:03 -0700 Subject: [PATCH 55/78] remove error file --- interface/src/avatar/.#AvatarManager.cpp | 1 - interface/src/avatar/AvatarManager.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 interface/src/avatar/.#AvatarManager.cpp diff --git a/interface/src/avatar/.#AvatarManager.cpp b/interface/src/avatar/.#AvatarManager.cpp deleted file mode 100644 index b55ef44c07..0000000000 --- a/interface/src/avatar/.#AvatarManager.cpp +++ /dev/null @@ -1 +0,0 @@ -Dante@DESKTOP-TUOA3HH.30568:1553633650 \ No newline at end of file diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index ea8cdc8105..33cd48a047 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -542,7 +542,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar auto scene = qApp->getMain3DScene(); avatar->fadeOut(scene, removalReason); - AvatarData* avatarData = removedAvatar.get(); + std::weak_ptr avatarDataWeakPtr = removedAvatar; transaction.transitionFinishedOperator(avatar->getRenderItemID(), [avatarDataWeakPtr]() { auto avatarDataPtr = avatarDataWeakPtr.lock(); From 9349514ff723d0696b36e89f1fae28279e2719bd Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Mar 2019 10:35:28 -0700 Subject: [PATCH 56/78] make audio screen inputs/outputs unflickable --- interface/resources/qml/hifi/audio/Audio.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 27c7048053..8bec821f34 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -365,6 +365,7 @@ Rectangle { anchors.top: inputDeviceHeader.bottom; anchors.topMargin: 10; x: margins.paddings + interactive: false; height: contentHeight; spacing: 4; clip: true; @@ -456,7 +457,8 @@ Rectangle { ListView { id: outputView width: parent.width - margins.paddings*2 - x: margins.paddings + x: margins.paddings; + interactive: false; height: contentHeight; anchors.top: outputDeviceHeader.bottom; anchors.topMargin: 10; From c3e5c49f693d33c09b7f9d9889f9683e71242373 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Mar 2019 10:40:39 -0700 Subject: [PATCH 57/78] update muted in AudioScriptingInterface --- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index b6254b168c..9f970faaa9 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -80,7 +80,7 @@ Rectangle { if (pushToTalk) { return; } - muted = !muted; + AudioScriptingInterface.muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; From 4ddbdbbb6c05e0a1e523a920ece767a7596b84f5 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 26 Mar 2019 18:36:52 -0700 Subject: [PATCH 58/78] fix bone rule warnings --- .../Editor/AvatarExporter/AvatarExporter.cs | 228 ++++++++++++------ tools/unity-avatar-exporter/Assets/README.txt | 2 +- .../avatarExporter.unitypackage | Bin 74729 -> 75752 bytes 3 files changed, 158 insertions(+), 72 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs index 4e06772f4b..1070449080 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter/AvatarExporter.cs @@ -17,9 +17,10 @@ using System.Text.RegularExpressions; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.4.0"; + static readonly string AVATAR_EXPORTER_VERSION = "0.4.1"; - static readonly float HIPS_GROUND_MIN_Y = 0.01f; + static readonly float HIPS_MIN_Y_PERCENT_OF_HEIGHT = 0.03f; + static readonly float BELOW_GROUND_THRESHOLD_PERCENT_OF_HEIGHT = -0.15f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; static readonly int MAXIMUM_USER_BONE_COUNT = 256; static readonly string EMPTY_WARNING_TEXT = "None"; @@ -231,7 +232,8 @@ class AvatarExporter : MonoBehaviour { HeadMapped, HeadDescendantOfChest, EyesMapped, - HipsNotOnGround, + HipsNotAtBottom, + ExtentsNotBelowGround, HipsSpineChestNotCoincident, TotalBoneCountUnderLimit, AvatarRuleEnd, @@ -247,18 +249,26 @@ class AvatarExporter : MonoBehaviour { class UserBoneInformation { public string humanName; // bone name in Humanoid if it is mapped, otherwise "" public string parentName; // parent user bone name + public BoneTreeNode boneTreeNode; // node within the user bone tree public int mappingCount; // number of times this bone is mapped in Humanoid public Vector3 position; // absolute position public Quaternion rotation; // absolute rotation - public BoneTreeNode boneTreeNode; public UserBoneInformation() { humanName = ""; parentName = ""; + boneTreeNode = new BoneTreeNode(); mappingCount = 0; position = new Vector3(); rotation = new Quaternion(); - boneTreeNode = new BoneTreeNode(); + } + public UserBoneInformation(string parent, BoneTreeNode treeNode, Vector3 pos) { + humanName = ""; + parentName = parent; + boneTreeNode = treeNode; + mappingCount = 0; + position = pos; + rotation = new Quaternion(); } public bool HasHumanMapping() { return !string.IsNullOrEmpty(humanName); } @@ -266,11 +276,13 @@ class AvatarExporter : MonoBehaviour { class BoneTreeNode { public string boneName; + public string parentName; public List children = new List(); public BoneTreeNode() {} - public BoneTreeNode(string name) { + public BoneTreeNode(string name, string parent) { boneName = name; + parentName = parent; } } @@ -732,9 +744,11 @@ class AvatarExporter : MonoBehaviour { // instantiate a game object of the user avatar to traverse the bone tree to gather // bone parents and positions as well as build a bone tree, then destroy it - GameObject assetGameObject = (GameObject)Instantiate(avatarResource); - TraverseUserBoneTree(assetGameObject.transform); - DestroyImmediate(assetGameObject); + GameObject avatarGameObject = (GameObject)Instantiate(avatarResource, Vector3.zero, Quaternion.identity); + TraverseUserBoneTree(avatarGameObject.transform, userBoneTree); + Bounds bounds = AvatarUtilities.GetAvatarBounds(avatarGameObject); + float height = AvatarUtilities.GetAvatarHeight(avatarGameObject); + DestroyImmediate(avatarGameObject); // iterate over Humanoid bones and update user bone info to increase human mapping counts for each bone // as well as set their Humanoid name and build a Humanoid to user bone mapping @@ -753,10 +767,10 @@ class AvatarExporter : MonoBehaviour { } // generate the list of avatar rule failure strings for any avatar rules that are not satisfied by this avatar - SetFailedAvatarRules(); + SetFailedAvatarRules(bounds, height); } - static void TraverseUserBoneTree(Transform modelBone) { + static void TraverseUserBoneTree(Transform modelBone, BoneTreeNode boneTreeNode) { GameObject gameObject = modelBone.gameObject; // check if this transform is a node containing mesh, light, or camera instead of a bone @@ -770,33 +784,52 @@ class AvatarExporter : MonoBehaviour { if (mesh) { Material[] materials = skinnedMeshRenderer != null ? skinnedMeshRenderer.sharedMaterials : meshRenderer.sharedMaterials; StoreMaterialData(materials); + + // ensure branches within the transform hierarchy that contain meshes are removed from the user bone tree + Transform ancestorBone = modelBone; + string previousBoneName = ""; + // find the name of the root child bone that this mesh is underneath + while (ancestorBone != null) { + if (ancestorBone.parent == null) { + break; + } + previousBoneName = ancestorBone.name; + ancestorBone = ancestorBone.parent; + } + // remove the bone tree node from root's children for the root child bone that has mesh children + if (!string.IsNullOrEmpty(previousBoneName)) { + foreach (BoneTreeNode rootChild in userBoneTree.children) { + if (rootChild.boneName == previousBoneName) { + userBoneTree.children.Remove(rootChild); + break; + } + } + } } else if (!light && !camera) { // if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name - UserBoneInformation userBoneInfo = new UserBoneInformation(); - userBoneInfo.position = modelBone.position; // bone's absolute position - string boneName = modelBone.name; if (modelBone.parent == null) { // if no parent then this is actual root bone node of the user avatar, so consider it's parent as "root" boneName = GetRootBoneName(); // ensure we use the root bone name from the skeleton list for consistency - userBoneTree = new BoneTreeNode(boneName); // initialize root of tree - userBoneInfo.parentName = "root"; - userBoneInfo.boneTreeNode = userBoneTree; + boneTreeNode.boneName = boneName; + boneTreeNode.parentName = "root"; } else { // otherwise add this bone node as a child to it's parent's children list // if its a child of the root bone, use the root bone name from the skeleton list as the parent for consistency string parentName = modelBone.parent.parent == null ? GetRootBoneName() : modelBone.parent.name; - BoneTreeNode boneTreeNode = new BoneTreeNode(boneName); - userBoneInfos[parentName].boneTreeNode.children.Add(boneTreeNode); - userBoneInfo.parentName = parentName; + BoneTreeNode node = new BoneTreeNode(boneName, parentName); + boneTreeNode.children.Add(node); + boneTreeNode = node; } + Vector3 bonePosition = modelBone.position; // bone's absolute position in avatar space + UserBoneInformation userBoneInfo = new UserBoneInformation(boneTreeNode.parentName, boneTreeNode, bonePosition); userBoneInfos.Add(boneName, userBoneInfo); } // recurse over transform node's children for (int i = 0; i < modelBone.childCount; ++i) { - TraverseUserBoneTree(modelBone.GetChild(i)); + TraverseUserBoneTree(modelBone.GetChild(i), boneTreeNode); } } @@ -840,7 +873,7 @@ class AvatarExporter : MonoBehaviour { return ""; } - static void SetFailedAvatarRules() { + static void SetFailedAvatarRules(Bounds avatarBounds, float avatarHeight) { failedAvatarRules.Clear(); string hipsUserBone = ""; @@ -905,18 +938,29 @@ class AvatarExporter : MonoBehaviour { break; case AvatarRule.ChestMapped: if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) { - // check to see if there is a child of Spine that we can suggest to be mapped to Chest - string spineChild = ""; + // check to see if there is an unmapped child of Spine that we can suggest to be mapped to Chest + string chestMappingCandidate = ""; if (!string.IsNullOrEmpty(spineUserBone)) { BoneTreeNode spineTreeNode = userBoneInfos[spineUserBone].boneTreeNode; - if (spineTreeNode.children.Count == 1) { - spineChild = spineTreeNode.children[0].boneName; + foreach (BoneTreeNode spineChildTreeNode in spineTreeNode.children) { + string spineChildBone = spineChildTreeNode.boneName; + if (userBoneInfos[spineChildBone].HasHumanMapping()) { + continue; + } + // a suitable candidate for Chest should have Neck/Head or Shoulder mappings in its descendants + if (IsHumanBoneInHierarchy(spineChildTreeNode, "Neck") || + IsHumanBoneInHierarchy(spineChildTreeNode, "Head") || + IsHumanBoneInHierarchy(spineChildTreeNode, "LeftShoulder") || + IsHumanBoneInHierarchy(spineChildTreeNode, "RightShoulder")) { + chestMappingCandidate = spineChildBone; + break; + } } } failedAvatarRules.Add(avatarRule, "There is no Chest bone mapped in Humanoid for the selected avatar."); // if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping - if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) { - failedAvatarRules[avatarRule] += " It is suggested that you map bone " + spineChild + + if (!string.IsNullOrEmpty(chestMappingCandidate)) { + failedAvatarRules[avatarRule] += " It is suggested that you map bone " + chestMappingCandidate + " to Chest in Humanoid."; } } @@ -949,15 +993,34 @@ class AvatarExporter : MonoBehaviour { } } break; - case AvatarRule.HipsNotOnGround: - // ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y + case AvatarRule.HipsNotAtBottom: + // ensure that Hips is not below a proportional percentage of the avatar's height in avatar space if (!string.IsNullOrEmpty(hipsUserBone)) { UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone]; hipsPosition = hipsBoneInfo.position; - if (hipsPosition.y < HIPS_GROUND_MIN_Y) { - failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + - ") should not be at ground level."); + + // find the lowest y position of the bones + float minBoneYPosition = float.MaxValue; + foreach (var userBoneInfo in userBoneInfos) { + Vector3 position = userBoneInfo.Value.position; + if (position.y < minBoneYPosition) { + minBoneYPosition = position.y; + } } + + // check that Hips is within a percentage of avatar's height from the lowest Y point of the avatar + float bottomYRange = HIPS_MIN_Y_PERCENT_OF_HEIGHT * avatarHeight; + if (Mathf.Abs(hipsPosition.y - minBoneYPosition) < bottomYRange) { + failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone + + ") should not be at the bottom of the selected avatar."); + } + } + break; + case AvatarRule.ExtentsNotBelowGround: + // ensure the minimum Y extent of the model's bounds is not below a proportional threshold of avatar's height + float belowGroundThreshold = BELOW_GROUND_THRESHOLD_PERCENT_OF_HEIGHT * avatarHeight; + if (avatarBounds.min.y < belowGroundThreshold) { + failedAvatarRules.Add(avatarRule, "The bottom extents of the selected avatar go below ground level."); } break; case AvatarRule.HipsSpineChestNotCoincident: @@ -989,6 +1052,23 @@ class AvatarExporter : MonoBehaviour { } } + static bool IsHumanBoneInHierarchy(BoneTreeNode boneTreeNode, string humanBoneName) { + UserBoneInformation userBoneInfo; + if (userBoneInfos.TryGetValue(boneTreeNode.boneName, out userBoneInfo) && userBoneInfo.humanName == humanBoneName) { + // this bone matches the human bone name being searched for + return true; + } + + // recursively check downward through children bones for target human bone + foreach (BoneTreeNode childNode in boneTreeNode.children) { + if (IsHumanBoneInHierarchy(childNode, humanBoneName)) { + return true; + } + } + + return false; + } + static string CheckHumanBoneMappingRule(AvatarRule avatarRule, string humanBoneName) { string userBoneName = ""; // avatar rule fails if bone is not mapped in Humanoid @@ -999,8 +1079,8 @@ class AvatarExporter : MonoBehaviour { return userBoneName; } - static void CheckUserBoneDescendantOfHumanRule(AvatarRule avatarRule, string userBoneName, string descendantOfHumanName) { - if (string.IsNullOrEmpty(userBoneName)) { + static void CheckUserBoneDescendantOfHumanRule(AvatarRule avatarRule, string descendantUserBoneName, string descendantOfHumanName) { + if (string.IsNullOrEmpty(descendantUserBoneName)) { return; } @@ -1009,27 +1089,26 @@ class AvatarExporter : MonoBehaviour { return; } - string userBone = userBoneName; - string ancestorUserBone = ""; - UserBoneInformation userBoneInfo = new UserBoneInformation(); + string userBoneName = descendantUserBoneName; + UserBoneInformation userBoneInfo = userBoneInfos[userBoneName]; + string descendantHumanName = userBoneInfo.humanName; // iterate upward from user bone through user bone info parent names until root // is reached or the ancestor bone name matches the target descendant of name - while (ancestorUserBone != "root") { - if (userBoneInfos.TryGetValue(userBone, out userBoneInfo)) { - ancestorUserBone = userBoneInfo.parentName; - if (ancestorUserBone == descendantOfUserBoneName) { - return; - } - userBone = ancestorUserBone; + while (userBoneName != "root") { + if (userBoneName == descendantOfUserBoneName) { + return; + } + if (userBoneInfos.TryGetValue(userBoneName, out userBoneInfo)) { + userBoneName = userBoneInfo.parentName; } else { break; } } // avatar rule fails if no ancestor of given user bone matched the descendant of name (no early return) - failedAvatarRules.Add(avatarRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName + - ") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" + - descendantOfUserBoneName + ")."); + failedAvatarRules.Add(avatarRule, "The bone mapped to " + descendantHumanName + " in Humanoid (" + + descendantUserBoneName + ") is not a descendant of the bone mapped to " + + descendantOfHumanName + " in Humanoid (" + descendantOfUserBoneName + ")."); } static void CheckAsymmetricalMappingRule(AvatarRule avatarRule, string[] mappingSuffixes, string appendage) { @@ -1296,9 +1375,8 @@ class ExportProjectWindow : EditorWindow { const float MAX_SCALE_SLIDER = 2.0f; const int SLIDER_SCALE_EXPONENT = 10; const float ACTUAL_SCALE_OFFSET = 1.0f; - const float DEFAULT_AVATAR_HEIGHT = 1.755f; - const float MAXIMUM_RECOMMENDED_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - const float MINIMUM_RECOMMENDED_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float MAXIMUM_RECOMMENDED_HEIGHT = AvatarUtilities.DEFAULT_AVATAR_HEIGHT * 1.5f; + const float MINIMUM_RECOMMENDED_HEIGHT = AvatarUtilities.DEFAULT_AVATAR_HEIGHT * 0.25f; const float SLIDER_DIFFERENCE_REMOVE_TEXT = 0.01f; readonly Color COLOR_YELLOW = Color.yellow; //new Color(0.9176f, 0.8274f, 0.0f); readonly Color COLOR_BACKGROUND = new Color(0.5f, 0.5f, 0.5f); @@ -1339,9 +1417,9 @@ class ExportProjectWindow : EditorWindow { ShowUtility(); // if the avatar's starting height is outside of the recommended ranges, auto-adjust the scale to default height - float height = GetAvatarHeight(); + float height = AvatarUtilities.GetAvatarHeight(avatarPreviewObject); if (height < MINIMUM_RECOMMENDED_HEIGHT || height > MAXIMUM_RECOMMENDED_HEIGHT) { - float newScale = DEFAULT_AVATAR_HEIGHT / height; + float newScale = AvatarUtilities.DEFAULT_AVATAR_HEIGHT / height; SetAvatarScale(newScale); scaleWarningText = "Avatar's scale automatically adjusted to be within the recommended range."; } @@ -1524,7 +1602,7 @@ class ExportProjectWindow : EditorWindow { void UpdateScaleWarning() { // called on any scale changes - float height = GetAvatarHeight(); + float height = AvatarUtilities.GetAvatarHeight(avatarPreviewObject); if (height < MINIMUM_RECOMMENDED_HEIGHT) { scaleWarningText = "The height of the avatar is below the recommended minimum."; } else if (height > MAXIMUM_RECOMMENDED_HEIGHT) { @@ -1535,23 +1613,6 @@ class ExportProjectWindow : EditorWindow { } } - float GetAvatarHeight() { - // height of an avatar model can be determined to be the max Y extents of the combined bounds for all its mesh renderers - if (avatarPreviewObject != null) { - Bounds bounds = new Bounds(); - var meshRenderers = avatarPreviewObject.GetComponentsInChildren(); - var skinnedMeshRenderers = avatarPreviewObject.GetComponentsInChildren(); - foreach (var renderer in meshRenderers) { - bounds.Encapsulate(renderer.bounds); - } - foreach (var renderer in skinnedMeshRenderers) { - bounds.Encapsulate(renderer.bounds); - } - return bounds.max.y; - } - return 0.0f; - } - void SetAvatarScale(float actualScale) { // set the new scale uniformly on the preview avatar's transform to show the resulting avatar size avatarPreviewObject.transform.localScale = new Vector3(actualScale, actualScale, actualScale); @@ -1571,3 +1632,28 @@ class ExportProjectWindow : EditorWindow { onCloseCallback(); } } + +class AvatarUtilities { + public const float DEFAULT_AVATAR_HEIGHT = 1.755f; + + public static Bounds GetAvatarBounds(GameObject avatarObject) { + Bounds bounds = new Bounds(); + if (avatarObject != null) { + var meshRenderers = avatarObject.GetComponentsInChildren(); + var skinnedMeshRenderers = avatarObject.GetComponentsInChildren(); + foreach (var renderer in meshRenderers) { + bounds.Encapsulate(renderer.bounds); + } + foreach (var renderer in skinnedMeshRenderers) { + bounds.Encapsulate(renderer.bounds); + } + } + return bounds; + } + + public static float GetAvatarHeight(GameObject avatarObject) { + // height of an avatar model can be determined to be the max Y extents of the combined bounds for all its mesh renderers + Bounds avatarBounds = GetAvatarBounds(avatarObject); + return avatarBounds.max.y - avatarBounds.min.y; + } +} diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index 410314d8b4..0da0fc0d9d 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.4.0 +Version 0.4.1 Note: It is recommended to use Unity versions between 2017.4.15f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 328972736bea1c62716589eb2c0cad5171001f2d..8e7aa0e7aa6032274a682af74d203194f4337538 100644 GIT binary patch literal 75752 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%0AaZF?BT+>&x;;zo5WZdp zL{3k~*jGY+VpBIGh#u=JhP*x~=lb0l+r{(7`=^Lb7&CtM%!CIx&NZwvoQEF>O%l$@ z+D@VFO_@%`+Qmg>biNEze*1;`xFZdHH{MQT)*X5KTeN)nfU)f5v$2C?96*ym!+GD8 zxOyw>;RuoA^HhYvwPw!6^O`EC!SBbjn}f-eDxt~8i*OOeXwi$^WfC~s2@GePos$&C z+N@a?d=%tWd&ZnSC9~)oA5*;LbDtda6%T2vh62GX<|p4+~h zvpN@n&J_6WPjjQG9)04&6RQ)x!|R**MV-eBjDeyIZC>MmH-#7Ct1Z`SwAo0vrV3%% z-&dJG$_8dN*NJp7uRtI^sUe>MNHDu4WDu&}+Pq&QLICiYt|KVX0RV6!zh(h?N$!g& zZd-OQ$cd)N!E^{ny6C{Knr4ox_<1{RIP$J$5F{15j)2;#*7N&OLUZ>eB!!E85ym%c zjvT<`K66eGY}%JA{rbhQ-w_*?14!U3O+{c?zOdIt^saq_&QET$!9&x7 z?o_{TgT)GdyFNfky?>dkJAM)^FhmTudr|9jf63tMZIul8RFTE)a+2`elGm}V1KWAT z)NzaU*e4r^OWmKJv#f?YPz2SA1)XcOGF2@GsUs}2C1Djx#Q!82j~dfoRIFOKDJNkn z3R9ZR4fQo4#HW219cJfcp4DvD#SOTmFlP<(?YL_|I(y|Y(@q_A7s`7ThW`xzavpI7 z!aJeQPb$$6h*%KbsI$g4@yTBn@KFdI%)8W!vsy`>za=ITvJJ!Y0ZDk-lNsNkJ@dg+ zNUs*^62sez?$W#U?*7OQ6k5(^p@meBevkB8(MmKh^jHQAbTdo3KTk zWwkVr3^Oo{IJS0e$Iz3>A2%s#P50_W9g(;$|UlLWA(dCHc6hG#XPd2xg zr%>?8Te+15;rpSxEC@S?tKr^yY2UUds$aZox7uM|&`9v~8_VYLnzE9>mM0unLdbqM z%6#dc~#x@s+hGaiXdOd+sxtqm~#0Mz?s z)8qDHvHX$4L(~I56u77=tLbqoYK-m9tJ%_vPVRX2ZnOXGwvDL|?4~Fz#zz;Wbe>SN zjfr)R!L{JrH`K3^8aO?0vopt2&Eoo;BBQXpEA!K-fuSdzm|l+-xU^~&_%2J=<6?5f zx6}-8w z@Z>xZXenjx&^f5A=zr&YEO8Qhz{Mgc4@jB>KzDuUum39r!M3p5^w- zuJ*@PUqfv_)*%9$g;BxpVK;g)#wB+9?eJ~aibkf`4VleBPHa(C>b=4BWK(wP2=}eC zn@2Gi_bWgFuOwf$yHGQ^lM_b#V@5#QnpV}L{?qjpFY>HA`7}Ce?WyZ7&#pl5{u>9T z5EG~wA&wlhsD>~glu9H5TL)I46kmnfD9h6i=F7QA_6>1n>ZQUdDs8t_SLQ=P&r?C) z{F{!&qvien5fqbEzgRxu4KIgTBR>Z{u5`Z5k zRD$=X4KG*iYHHrd8zh5j`s6l90?%g0K;Ge8vdZf|d~|py*9e&UXH-xv$S8t)*Tf>~ zmr7N&pFwVYRqQ_uw;^PHl;8fav1UAAUWH*n?57|m%+kKFDcJW0D zW1+|uk6r21nD1R>`?bCCR(JN(FHf2Yhu49ioKB3u+G5@N^8?1uSi*{a(WHvhwQKY6 zo~NIQmVgn_n1MMKfvCqJn8w{GaRAx=js9_Q*kd0{p#;UOoZBB~o%yStv-K=b#lDZd z(q^tNTFksr?x%onWDjdoO^oedX{kHad#mxp?L+2dpJk7Wtm z^d|QJ2vjAup))29V?T_fa5QGN|AMHXT8hNx8(KPKn7;}+l15BT?(Eq{L<}Z~U!#i7 z@d6TA#;bj+p5b+@`&`QZ@@2`twQw^Y8m#-FGu1I$Z%gn^l{56)u8zSdLfHsLzv}@M zP)~Tj-KdIQI{T-G5c5cXHM*V1P3+z5!{NQ#`^q6!WZ*lEXNjhl$@o0Rolq1&|5{HI z>yF^VVQsiS5Gptv_(mNk;Difj;Gi;;*(>O6@&hHU`4SHk7I0bCZzV-IKsRELHD>fV zrY5oyZjM zLy@`BY44m<&$pb3D}Uhol8P1+?b(A-OkigwtB_Jv$Srk$RGXD%ymeoHw3s^_#g30$ z7Cbh??vh~JMKS_1$MahllK4iiyU%uy7qhFoKD$RY!&ys+|RbpsJTgvB2QQ&d0VSluj=_+t|Cud$W>;|pt5nU zRQX($Th3N)iKJP2D2fpC3OF)oyYCD(Ir94CKH5<|V#MS$gdzh9JB14`J#k*GRS{!YexvWJ}p*-I~k{Hn;&@P~d`(O94ewBVGNAPIG zI6i4{G~p#3dw0PH8c$Ahlr-!*O}GwP8SuJUcD-_Qe13G~dVg=YlY&hO@RswImxyQL zGhlLRYxmq*9v2vd@8!#%13$+SO@T6Vt}|IfcA|1PyUAg;qP4*bKZcdYkjbh3SGMZ{qu)Oi(bt4b`1M z_^d|fgBe!&{%2Y{2VZGHou=}R@6d;LJZ=kq#fWo`P}ePwj-I-{iCgV=+xP?7eD0Ic zyj)!7@X;|hw;bTD=e^==A_1>m4^?TsxSQi;{x)FK3c|H2;iSyj0q|`3C=5Hs?13ArO9Ct3QhI%=`Z76ZoZz*(=TcoJPidA zb1tVc%i9$5Ral1S(4f-7iX6Xqf`6Iddb|X*6D~pD%4;@_{tD!?-k>;v8|5>jXLviI z4O_hBBwU?Zu3klSHgq+?-l6Wz!?&HmQPQR+RvS+BplB2Lv8wHUzcUa<+?|s^6jWB| zN@Mi4%=lAP50e_6mZ{&|J3vSWt&1sM2crG+D%LLGM==AWqI6UY|wjM zy*ij(RsDvwV^*cp{H51P`N*UM=0*)R8xM z3`1PAoBc7itIx*U>Xb-UffejvWN%eHX@@fI?klJSVcaz;}rZ~z8d;9GMJjXs-iLPN#s%n4D{Ab7v@@<$!Hj?UM9)*`_g`UZqqsjW7&onwYvAfB!Ju@JnL3Qvt8Sb!U00LUP6x;u%Pm%`!I<^ z!WyesjkY~qLL76la`dck7F9D!*pbY)1f<{Y2B2XH&qJmd5z_du#j7huvyX>Mr9?|% zqHP|9QI%n&(6mw**<ssoDl=-|3(fs$V6t*8I&HMqW9G9DCRN0?q0x^k%yt+RzraBW5j|EeY z#YiVAt#1(M4CdIG%H^eoA~47ZT3?=a(qXUQP$;jT+EXJ(3SHWK(5R7LEL=xsQ};lr zu!Ffhzj4uEN7N4MjCAIlsefK_9J|Zol!AiCk5v+r)d-S@45R zdN!mZe$Gj1V&&Jj#ET>|L!wSih}5olE);9zfGWd|l_2kJW2tej0MIP-(pRSd_vb+K z8L|%z{7Y)~S^{5$_);YO7Ey-8P4f2wXeE6^j%z>)v=@Nbpf^myQl|7dC4DkeGA3kU zIt7yzZ&F@J(wt#Vsf2VxWbwsw&{TF*ELHW?8!Ek-z>#LmG)a%gYTDAg{*oj|Olf`5 zEb$$Wlvl1Dr1!_my|(7XRs$JrpvQ%SC_HNHBAhY)niLhr>DUdtH|epOu-w%Qz9*^_ z`ZFKy$oe7%WODrD5AiO`8L!% zWlM`a?~#2c&1FvD3O8qG58>L7-cZS)h2*fR|xH6o^%BQBO*T0O4Kt32&l; zt^tf8DG}?)352=M_BzenFS=yGz{Pf5PJ0*?#uqn0G8mIa2hF@_2B5CpVa+5FkOKTf zAtFYgV~_vP^;B{CW2Ln_d2C-!6LD=s{_)4wcf11Da_X#DT#$-4Yw2L2g$d+nDZiCT zH0Q`R?7&QnXwfs}Zd)S(eg*XDcBpoE0i0ZJ_#6Q!iNdecv}E_ZC^dumy;T^Zdv-J* zs%^>_&PQl(!4;D zFE7ikz7cqv6TBVacYeK(jl@{TE+{b0KSpA3m^`XiFls&~GPLfGTFv5&3!LGJn7G5y zY+6Odr%386jT4KW$bMDeV8>Ew2W{Yw*Ij#kHv{$Lrj_+9i>}1H-tevc+Qko{MNn#C zZ$b}qzL>2oYf={8y(v>vROmICBGIxE+rC-#6+D#|)N%!sItSl>)rBLBqy~Ae!B|2o z2%VcF0c83 z7DQo;jhS%i57Ay_ED$Y`B6(9eK)?uqLM2DWz?8)ti^-M+)=CU$>?|VNqiQezIlEi9 zv%0hW<;hpq>ugn7q7^7P)lZC-Oju%!n&Tft1C!X*OCJ>{O^K}=qQ;DDw;U@PIY5og zl25@Gar1S%9B?LM!oEaq5pp0En!XG;>1ZPDX#O@^i`MJ~NO4eLqzco$#5>=BC~4G* zvLp+^M=yK_yr9enXh)aOw;IVrhP!F3E+eu9_II+ZY9#WKJm0C~Dwx!9L;%=8Y$faO z-Y~4ZtR1;c_4xAq^wQ=; z^Rlm92n>q-r6{UI(K*r({j^+78fr-oMEK{3M+8WSbK2s|VTdyJ+E5*|Z%(GEl)e3r zUG`!GvUUUHoqX{u`$AKhpYfDz!K&|-(v|z_6eXC4p)sQAGE)&g-V=-;nd6Mo3lV=! z`2gY0@d0l$MI1*R^Fu=eLPmGb>4#9vtJ-o;-|>6iXDLt%ch`o%8ajGwSGXv2&bzW^ z)80WSkiNMiGqS%$$?nIRd&+pOv=ub+caGXa0n8us`dAXUJwrs1k#{dB?QOt!m?-6C zO5t_$b5T0SK-?wh>Nsj^nNhSx9O#d$c4g>Wnj1? zX1#`d8}@P{WOLHbjF|tvJq)^sB9j4V)-V?#X|dvew9l*Up9M6^*Yt?^&aq}NIKQZc zNv(>jy4ig1SZII8>K0v8;sry_i5tdfW=smDq{2pV;DQPbK|dxLBbi{@@k2r?ebfj{ zTOB%rmcyJ{-3^sT-^7*o+%K)G_hRO}PhHOGS5TP1bfJoJp-FH-4Hp6w15xK=l;fr* zKx`41DB{NZZGNO@p|CN>W7vWXI9}LUUln}0W6wY;sHa7IbC!vteC1Fxyp~88z)*w( zrwDBzneiRftuu>E0*93uiLIK1nWG%QfOvmSS5oCfes%TN_%^7 z3uT!+WPoVa0qj3@Bb(G>Bh4`2|Fl6naBKnVJ+8SUFQBaCM>= z>W(#H>4#N|_uV8M+_JN;HcqEC4X@!rMn+&3D)PA}$z8_+&GvIAl{!o2>lSQivss+7 zF}hO~(1u{!FU2+zhvrDmHSud&vaszu4G8@%3?q6H+!fmFq*Pnl3Uw<`1XSN}qsL^yqvP;^ z722h<(d0WmuPk2yY`#V2W>?|pAnp(eU?!X?TEZQAK7#J3?ghkv$jr9#FGG3Tg&f$2 ze>gyc^)hTUp%p)OBtkbvC)Fxtmyks+0tzp1s0~;|J>-hRi)z3u1LOL6vzD-2Wm6*R zd(K(p=wr;d5xST1WCvv5h=)&mlY!EkczvjESm9C3=@r=X%W3VhN-#=WJLLmPZ1uO! z4u?%i%{?C`$xeJ+ymzxnwk}|LUs>d}8j4o01YCFtBki;aOy&qRS!*!{OH!0CG9+=Y zy&KK0zbt7bq<1fR)p;5u_~PXQ6;j0)@XI6ytBJO(s3Hz>TYPjp6J}q0x(c>O{m|$-8H4y$pbYMtAjd(zK9RuxH|kZ+MSp*xJ=hPbQ3; z$I3jRjTz4oVrAYZi+4k1vM(mD#^4n}=L!&b?)9QWm~&maC#apGE8{Q5%kci-M5Fac1bl`h&M%T8%*r>2X+XvngYF;pjQ zF!z8Bba5Dh>j=YsRqZsJQ^{rC&vt6xYxqLGT_+BjMoNK7+6XU)wN<5I8KsFCrl!7_ zJD4lb7?@Jal%m8%V>P8TN$`KT5nNtcJ&I@m8zl?oO+XhEEAZs1VWx5zvz);hL_-*P zl2O0jqr%^(G)jE_LeksE*QNcKureJQB-sv+kuQedabTBC7k)6y1vhOXxqx3eo6_-C zQe%JCv;{IJjU28Z4B;&Y83(R^+1OG{aHy^D>pwHRBG#EX2t{=jzB9RBLF_C_M^ho7 zvG_v;{Q!Fk4g^C(T8gqXM)9dTK3xVriF3r_U;2tKd4*A)afLRjlO-uSSUxod{PB{MtW7 zso>&pb^5%QYrdB2GJ=M;R=@4GhAKZosBFPb75XeZ@L~f)o{_Y ziI~|N^fezjr`v3FO3;^E@&GmH17|)$InYXuEDjbqt~*;&AuV(g$j+v!PMT=?sw{!z z7~TzUq;q-M7O$;Tdan3{t+(_E_=))%0}rmgZu#|Oe?bIN)HW1NIS94VV- zoWxD`UGL%RV{Y@}+L*5oJ6xID1`K#gdt5COr=x?Ws(oUD7MJZ|X;pht zGj=~5cCh$)hQm=;hPHHogT*av>McfAi%Ccv?6mq>s(jn^f@R_7!C`Aa=ay{FvS%ej zHSL}igwc9ZVn7Y}mRwGTCEjiLusr&r`W^-*xxa^^T@!RG|22tbc=yMal@LUs!)Hh& z$x`D%t*>QXnsliqakRqIIv$WIJ7d|+Uhc#jmNZV+|lD!_>he6)dl1} z5uf%mmAimmo0q!=t98W^Kxw7Xo{Vy)bCLiGF(YqVrRVK*-FZ@Fg1d|wbTso*y4+Qc zC+?appyguJH#!Xekq41vc_f_^U(&uw#(G>1t#uTA;6wj=&2uJZJH!_1#|_L|+~@0| zs{_ktSL<0TfPC%%(I@+A7UR#yK?xLUS#gZ14!x3n)5XvH0`5=uSJ&-|tKXVTLElP< zthOX9demHg^PkYn2=Y!+g_eMcSbXHc&G0X^(8${J7I_cPWoeXM}3OW@#~0% z+BCw6GHI!%jgS6Rwo7h;PuE?;d`Z?*fX^z~q735%YZWpOX63Zys+Domr`$gruH>zB zy|I?-fCXzu*i|qD&ZD?9<8=peUGEdif8dq#7L+_kAy#>tto#xrA%g6NC95_VnhGfg zW&YvUhlnC~)KtHzsawS#i2puu*Q3-#N??=!6HX^Iq zPUm%_e&lv+TXBDAo%xb;F&8VBE#ST075wXItXVzZBTjxw?ND2%!gWov!Yv$i;*PzQ zS5Dnp=Rwmf9g&2L-|cEzotV!JjPLbPHf@5zX7K>=6XIMG^z3tQ1h(6>^<&bGN4ZrK zZ~>AZ@OavdJWP=DwYF!Bam&lZdHJqDYSnkdZexUV)$5?5VY`1+{kQ-~Kejx$rcsxu zwf-&qxU2MjSo@aGg+b@5@vKW{C0+-^6&J**$a^cE6*zId-Tl6h{p=Q(BRp&Yn9{*7 zzx>S%tQuN|1F_fL zR&sw6VwphUPS_aLqjhF$rdeoAPF;1jL`M8<$dBc!fuvjX#8-g*{Mw)DSuUGmRYi_h9^n_wPP%(v`= zx5hCJdkpK*!EbNOh@6m>#~OW$jWD?1eOL=z`Isf0^1eIz7#55|1{ z@1XJu`u!xqf0E!oN$`I|dp}9=e|CdEN${T}I0H8mI|m07+wZCXGIO)AuyV8fwhZ7O zKmIcq@qg_3@886K|DpaL8w)$j&*#5?NB#!?{dX>npZxd#LjF!C|9kjv_P^vmSvfhG zevbctNB$=N{pasgKS}Wa!TkO4|F7o1Sy(ukIR1+NX8XDS>)(=pfdBqO{XZRxWX%59 zBPdBfk?&wKEi{O_$l%dP3eUPR0^ii^oc2RoBCR;V;j@eyn73fMal{X@5|`=CA|fW! zn4SAlGnP{}AWREV7Uh}+1X7AW z$i;Wp@9CuHiEg%1$U7iF6B|Fc7M`cP{rO4`fwv`;vSgg$5#3%Zp-<%M)d)^#g9TXr#X;Up9 zVQf#Q43CW|2R8%b-lJ);aNaB(Esia_SZ^+&J2uOOuNAw@gwi^A_e zSq5Fz4@Y1)@C(LF%fSNRs>!E=MZh0sVG+drwLKuD;x7EHm=PDKk&}~rxLpp{>8i|o zgw$YC{o-#<7+0Hb@S^cz4e!HkCie~d{OJ(msC(k5A#2|)=X{z}z-Wd)KYm1Q=gQppu?Je12s7?TkfDre zyN64C$K~nb)9%vkL$1Ho8OUk7^zNFKV_^He=w8*C6fCuAF$?)zm4k&*BXS;3G??WV z(z!!HSe}fCPsaLhU}%XYI>tC{M;RH37n0fdt|8ZkJMCk7P2{*ILS&ROAPnOxuZ}65 z)3@|s%e!HoMA_sCVkH;{{b#u}lVHAW>!Wv&-S~SDSNy8ItK5o%!J#uSFleE;JA)Ma zaQEU`DDE;qainSj-o{gy71<4O?m$e zWOL>;*LPn?2VDNy9sWxcaM@im0`BnHd`CJKBw4U7Ot&ht7>)T}I%Ek%^*Fk;^xIw_ zC-85UOdJz9juDvS_w32`lxBOmy82HWGZc&+iSW@CXfNhCOAg^OH<+ZA#I%@Z301pL zmLHeWD{wc<*Lh$x%Bgt0oA2V_;NSCqe<=JsHR_OqK~QEvqNA>u&^&Pv@`nW=2DYXmK83t)cs7Y4AO@b z%eqmsN6r$`HY{xFcgr~BSGP7JJ^3Ko=jL_)y;r|kX_(a@mj8nhalDN$A=9%W3ES6d zPkVOw>dfEw`51XU_zy&cia@E7vIXs^|9v{s37gY)c06JMV=f`yyiI`6z}l>HL=_?+ zo@4z6>JBp2Gki=v>x@o5v*T4E;6)3_PpJ6nbk)B^K!B?X9pnYdC$u^AjpETO3UP5} z@gt9EeyCfKgV9=LAtRTR;=q#H(vF{=Hg~Pn}2|n;a%K<=NwAAXiHTG}JA9;~%L-dpetn*k=xsNB zFGE`eX!&oZ&%hDx@4+%^Ao)DkRzzK;orCmRukt%?{)~^xbq1(0b(}|hc47p!ADWA~ ze)wH2?*DaOJ6>MUxBrWdATM6zjK~&5raY&sy{fh$%8VkCMI|C8cHy%&n7%j5LV1zn zi1=tsXg#bf?NrC$B-_E@>h%jF7GkXXZ}q!1D!1xU(0Ryd@;hI;f39PF`D(MUHapP| z-#3*oJwH=OZt&I@OnW3D^GA~<*&TfYd)88XeO^oPy{=|?-dIgK3FpE#ecr7TX&dk6 zz=xg9-(_`fm)zU_+Q~Dm*7;o8H~y_Z{E#TW$rlf_swB7Gp3-Gk20$G?xWnKVkwRII z3f;(0s9&&9sz*NWspk@C3AnJx??i6S0JVGXVszRuGZ~SQ6K4}(+b5$J!(;`B>tL^{ zt-HB{vs4qP)Aar}0FyE-l^-s;{iHorVqw0S>NiS~ONAvL0gYZ># zr+84!ji;6h=25IqD2tS9s_Usg}Y(VORLVR)h5v%Sb9J+}tw{=Tuf*Sy|&iZL0H~dL7 zGVz-1h+xT~S*urEQSO7ZMjB;D@SS`V6`>csz?uwMq&j=j>)NJNeF%M|#QVG)%g7&= zwZ^u>M0_@QTLd$o2)UxxfEBsWFbWjQC!`NKKCYnqC)z0CPY^+`orIi%S}r`SBorzw zGO7lPx^9TdW#`*bm$#0t^Uq3l$48o_?l(SXm%Ddkj7^zeOo9~O zdsiS6_p?7DE~@nH*G9W4%az;u?DF5H?+EFns7j$A;x)8!NMzkVU{KgTftx^*5u<0%*5%98bu*=n7_%Cb_tn+kM#=_@P4z zOG$k*Um9eeXn(Ks=~3drc)POF8y0BMVgyhMg#QuB<%6vZR%>Y1P!z)W`kOg0G zW|vo`|1FeZT&yJojm`5otrmJ>NjL~mxJ^K|2^L^`m5DG=tn|gnbkDZpUn#afr^@&s znl}$3R0VXVH-8ysR()4L5>}~|t|m8+HJzN_qWpTdKDWxeMhDb;Q5Jl@iUQ&9a*i>g zG=wHRHFJ~JczM2f2%TT#sR)9Pm|ju4JKY8}RS(@_e6C|Wxgy6mv5&qkVtgDV;BCXR zs73ZKE1WQ{lqQ*EAFQPwt6D`QnMHG!Hz^>5*aGjv6%7biz0L^d&Hq9U6L6~M9HK{L zIkERduDYur;P?KdGb;JD-U1lN^}5+ut5bGgki9df^;pat_)fyc2~rAB#(<`g*9zGg zMr%jPt>bvrey`#$zPK_&Q(NC1{d61K4OGQkbdLm&Ld-6D+# zZ#{ZD-4W)vM`gU)?h}NnI2V0W;wL4c^c;e3<-_$R6cynIg)SNnjDN`R=9D-@ct6#p zEc+;}7>a#jee=XSY4tpkn!3Vg;G6>B3=FT#Ex|5FuW^O#5ML*^Um36jmBjtIx3d-= zF%KJU*sf_WpQThS=RejkiDD7Le(wv{DCEPt)D9smNp`#Ot;i_pb>avyEVsAzBXdXt zJTdm4#u*j0r|^IYa(7nfXllk_E;m|uJyX9)rK#d5{E7RTI0?JLYcLtZoZj=OhVA}4 z{i=o$ViY>RQ?1)(mN!RJ!p6!Rh8v})6hq#(bLHRMICr*U-P}0XuUb6zpkYDGkt`22<-4V`rYc`5(31|Li)nQnNlg}u&FU1)buzAN zN)KcC{1~qCLuHWE&KpS^o2$HeK<@Ax;iL zjTPfKy?_MJ4W})jt+7$=LVIk}e6CFjdJ-mrtU9};z9r>znq^U)<-9Q!L^{Sl-l%8c zxroVkBcU}OOzP*oTeeY6whkl*am3`=GeH)A^7>Hs{fftD#cZZigR;DZLik(Y@B3#iNegikuEeq*F ze1|Jw<`&In{63$VXGj)nAR28-m((xQZocGeF^mts^D2CWR+W=A!8e8~Q3GwGa`nj9 z$r=VPx0kc2122u!K1f_LBxTknqg@OhN%lD}$st3bg25B1sS?b4CQ4P5@7nuJtCY3A zZ~tUXw98gYfR*yjKwKbPAY{z>O)b0XHW!0THxlqbA?zDzi!uvZ{9m~qs4*iaWuw(S&nBJGbd?1fZpoen;eDG1)EaKn+bd_NDMFbOuTvJ&iB zvm539LKguymGeRzo^?g!ZWtfeF{0aZE{p{24O67~UzL*)-QA5h;7|_k7x!FWHi3(~ z+%(XcaYbmZ5Fu#9UjYj=xlxL)57?3kO2d;=9-^NZs0-0)tkdz##s)rFRCJhi;oCan z2W7Y1|5*?CTh7B-H~BpQRA5b7MUQ4FS@X+jPcBTA1#m7o&#Tayt^%;_{yX*~1Wsid zsKy?MW!{%9R~sdA;I)dpH9uo1i<>&Lte~DP?4e0~!!3rwiaK5>&596IZh<-0(nkyS zunpt0I(gZI1UJSo0IqVKxTCl83bTzK7ZOO{xr5XEuc1iQY*POw+a+^_ph+%<4r22H zDnKS_j4j3v!F$iY4`+Tm_KAdPBp1=HsG7u+*arkk%*EZ#(Yb&SwO+|8jyHBvjAL$U zpy@3(Au%Ud4JY#j&VFR8iS_gzrREF^j*x0DGdIoE8(>{xY7S7`9Fb?cC)e#_QJ7?2 z)18jWV>>oeN2fK+cG~TR37!*pT$D*us;hV~Cz zriF?Csdv&=le!h36?>jw-+X$ z``saJf8R(1denh7LI=(dUUc)QG)bM2F~NZPc~vKJtkM zJCqo{1hf>epK@R7wog}gTBT5b$zRh;lD^x!+gy@Q4XL#U|K?*nHAQED17TNWXwFItO!U$Eh&7{=B6MrCnFp+v*3|Hd9}gvSnPTpqM`D>>)+vZn%i$v z8VC35j$jY%B`f3}j@i)xv?a>)63>kJ%&C(1N<#BcDZOcITShxI+a)8{D8(fL|{9J50ikRmmGS{asm0jcCP`u13I44 zlv;_rRk>*})=*H7dD6+EDHei}tZ~L`_I8D?ihl4;=l6|U@GutSS09H$3i5_A`ljmX zx2_M8m#yh`wNs^5#vOn-Omld*()^7}wRBiJb&7`M*9p2Ii zw^5NOFC=d47=V@rb9b9S5br+%-^-%kq**I=jzzbW3-9H$%TOjxVKvlCY7Mi%2A2Yc zPgXlBv?S<;i*_rfeaune)&H%Jk7zo0_I*g$06{G_1} zg$e~-h^V7-%oopI78W8*DW_jvt)z+J@E{NeP3{EhM|@%p;uXztuNiWg0_aK@h|kWq za|E#HhU$T|Y12NgQL@2bi)=;={^%VCiOX9`qpxYJ@&TPkrq`hO@Igyf_YD_Utora zY$kM47C=yzMy9N1-;pn#z(kTw{0-KXO1=1rBNT?wN=ekGB9cNm&-pS-%V+4q}(l0(J3R2ok<5M})PtPW&zJ~7c4-Z?nHTvvcY+Ll1%n1Y!+^{W=C_ps4L^8WE4cEjlG7H z(Vf|~+fSSxR0tjOogm=kI^dWH4M-yF2^SoBJnb+56m!f z(R$$ldUV9k6uowTku<(s_Q%Bff+o8X&FO5mQe3P&qAP^!NBn7a46@+hibmanc*@db z>1EJqEu&Eft-84en>`hwC3%#;W^5OT>wRVOzBJ#&2TGwp#IPPxO3L1ax}_VNP4AcN zyZ8wxdM+Gtc4yk((j8B55DOV7EJ4Zpr$k;N{ovbZGs9wECa^Rq202NVAS3|Wm1|+h zu^?G%O7GM~xVCm0c?$g!C(I4no&lWAi^j@RDat{_$1=d&;Q{WAY1bAKheg)qrI=~4 zwet&}9tA_Hb9pg;+u5Rud^NGttytSIcz}sG3@JFD{=l7fxnMm z7bkmP2ET5PM;u$aBqk}jQY#`jf40}U>|#8K)#?=~PC8B)KB_{LOtKM}p=dpc%~2P5 zin~yj|otz}YrC4WrUm4b(@}aLg=acj@9S+WEFriH8 zJ5P6#jtdB!c*%xWs)PIcK-(8`XLEq%+*V0Clq-GlqjboYcdNcJW&B%Q8%5oH3u>~T zH`y>tfq--0GMb>{@^fm~+h@I(h@AD$NZCSPZeMms?~aC?r8a`+PRxAq6;4%X2=1|O z74wi25Si&tXu%fVQS_L!0`!yGy6Bj&w_Dm(P{56sk$R0Y-^`3FB=mEifGYFM5*W4@ z&N%d&HxlDX5pfy)jvRZ@2Rt^;o-HJYwh%`H6M85JwLe+A1X8(4I(s5(G*_}=vZCdezW0hLZND3ToX?bmnc@VCrN$x}xQtf;D>K)hRLD1zDHsHjUy_J@T zHIRj)FTNzxE%5Dn*ouQciRIqQnoY5*gkRb&VS7Shim zsaXq80U_oo^D0!T9J)3=A9EpjERjT9s0)JG#>EUG6kO5E>mnjkW7|qE=-KuzdFN#~mGZ@2zd$6lB z0XMN0(N54CV`!AE&<6@TzFC++=%B68cTBQ;e(Sh9)Tf8kj_$YOoZ~N>fdd-~o#uKF zsN);0S`r4{&01rQj^2%@L}q(6+)9Dm+1UR6LPd{;?$hClq;ElI8d?hhxwgl1;*71W z6bggV_(z(1zBHTzwBRZ~5?$w--=KriwycJ@cuV7>QcSj6;`6w0@vd*CGaa5t8yjI) z`_6tZLNF$xmjN|ejzB^r=WyJE6cJ=EjqnD2bx!U0J~fdQtI?jfQ+(As?Az7+>Bt9? zZw5X)ZfD8!L}DnvmAF7&LxJ62F>&UIEFU?9HYEcbg7<&tbY1PH-Wi3XPsAA02T|@<#i{FdGe*?BYaOEb4?Qw%t{lN(45XkKDU2leG`0 zx=wE=gczCz469qwtp@t{4oiY2}0UAs|U}`S(oX67IQx?R{rc|L2Ai!-dl~uzviXT>;LHC!0&@fkR|$mOUkhE&4Z5 z>e@*fE{3lqz{Y~|ByR8Au&M)96~#@^#$>w8h79z00XVOBqZXCgMQI&rxbAgy)tB>^!7v}=kGzd zSu~TMP$n3>bZb@2Ev^%v9gy<9<@=*KySIu|h}uAL54 z^CW)LXp#jL0*s8IM63i~RsVVkhMCP71Gxp98r$*r?c;VkXqx5$R~&0Vv)7#wmi8wH zXOCi(TQ8%vyN!^?yZs7CQtQ)JjHfp7d}@lGA{yIR>l{t%P$cu!JNUmF$kVTzQ*153 zHql0u!&MX1ZkkK7hV6TMNp?RbK-c|t)7kSM4wVxh@WNmQb->+UiRVM1W2qC^0?6cM z3H!wIGcVguSC6;07pRR4^KD_PzUuea;($p4R(#DrV?i#$<|&A~)31tu!5n7Qucwb^ zXTN4R0&}n2s zyePA@v^?bHHApPCxdS}(+3c7rlhSFCvEq z4>P{rD5H;^KU6*DoBPH!)^PGm>~v@{f3#T5xZNYF z=>A65Ps}V`rM!h>#Y;+|rPjKkL_igBz4Ws;!hEbmRQ&es#uy&JP(F@ItfkiN&#x@% zn?KpZKhYf%Ye*l5y6g9f&`bJFe!!j=Q&OI%-xUWwwt3q$+*0nd;RB+Fx))+`?DRMEBQk1NB?9|Jr=8P?{q5u#Fiw5lGNLGp;X4++2 zgLbJd(>{Ttyj7nrh@cnh&K-Qqmc`hQHOBxEgllLe!0+V2vwF`uHtcwJjIc7=u&=}5 zjP~!G<_WD;7xq()dBA8HjthDb5uKM^WnemIiF$qtMEZ9_ zI$?(7Nw+EGqJCD29ER**vAK=-e9=$r3hlglT;Jpw??kCn7lH4EMDUy^)sa7)>_z|E zhhjGCI{r1l>UH$$v*ct#Y9$}84Wm!UO9EI&7dp3dDD9P%6lgcuhusYDDVU0xIt_RM z|MBYkt$v&h+lcd;ul@REM?!vxie&$eJGyG|=07Ls6xYP(hgR0RGb7=FmgUk*0_U;} zaA-94rWi14x_ZzG=0R~`el*)0UIJ#Q=zZCK1s7+G zS+v>Nkk2a9&Ips9W}aW z(Sl(Hqm7I~Li7?*B3g7t1VNMpK}3rvAs8jmTl6kS2*FM^+jr0Io-NruyZ_%gGxL4- zmHXbi@80+O?l+)6f#{#yiva$4fIwi7l#~?i_t*HN-``$~ii5!tVq)SFV4Pf36eJ}H z;E(|P1bBO)p(p@u1plA-oA&pFqMbdU?r^|A4qT^y!~T+gVt+A7Q856=PqE4Wx&0wt zUU0M*;1~R#0wg9XD(WBsf=h|QV3Lwx7*rYxlW>rPO2QqWQj${tnfNaT28;g^|9=Ml zrv0Jc1B9?U91R8hqxf6#UrYl0Yx|2zfW@Sw#BlY&VDK;T|EIta;Re@0qTHcqGdRi% zf%K5&5F>N)M!;k_{?^tYb40qq;HY1pAhH~{;a+5Fa7U=O8(PcV6Ny5@QLm8=z?E!Ox>*I*VpUTXjCE7yQ3LR9sX_TmmW$l9qCm zl#&8VLSaxDQ5Xnnj|&M74h}z`|9$s=F>xueU-;k8z~8k0e}?~wOZ^i6e+vEz|NC28 z;}`z-KY+hw1CFVIXl3 z94svZf`T2O_F`f%X&IdFJ32_ir2j+wPeL603;+8W_?!0s^+^o)r|>u8zqpj-Z|sjt zMo569aP`F{LBHhxehRoPAbPqSqQatN+$IoBj!WK`IM7fhS#J*n`nEVs*a7J-D296< zD@!IMB=o&hOq7F1?8i$&GCe5nkU&7)zVke`=7HN%9uBy9{f4^!ZqjNW_#M8B^n8inh^1nQ&*_52NOh9SJZD}enNk>PjO#Pw};J3h4<)p%FMaPWTtiB91HgUf%?MOWnigH}!-#Y*oD7J#ngj-2bi$+8YJ`<0b!n zv9SZx?R()Z+_Qop$8FsAAC?+8I^rV7k75w6_Qci!P9oeOZuW2(^5^Q*gL=9C zM0NV#AFbSe))dv<5nkVo{Im4wK@lFf&VPcQCiT+lm>WxM^I^x<0OZ?@n{bw!IywECcxNwGR^IOpm zqy4_>AIT9Oa4)alD)_zlwh`RX4R;{mRv%X?`ltKg4|>-_!oElAKQo#!8U^=oLOcJi z_3zjByE>5AZ>7fWNF>_%`!Ig9(5lW*4-dH8A9QN$33u>zgQBkD;=UX1gZ{%Y8KWG2 z%=q^rQ!``uACA%TItqdQ&D4LKe{M+B?^gcDEzU%!?*o$&m6DLgWyNsk)vs?Q#l$4R zVuBp@-|xhvC1fNdq;Z8%TyKBo`hVEbkM=*>{kHl)#{d4!`9H}Y|Np@e4GBrG#P{ca z3GrX@e?J9(CI9!gwg#C#6pirt(faSm`MKi`iysn=-^Q?CGJ*eb{4FE@l>DE>pXL8> z34mYx|7YN@<^M$f;F(I;9r{mZ0e-=MBmTDkqu@RWxUaCMhZEo*#9#M+F$oEA(LeQn zDRI$X{Qqa*o}s=bB^mSg6O~fys+tk*H~9O91RwXUYyBk)0N?;|4fV(ITj5TKYshjtcJ7@ZLM6By;>Sc-?^wSA*mmVu?U3xv4nwvXg&}lIAB~z!Rr(&A+ zaZf3AU-{(BQbpDoeo|b0#kPXi*)%_`*YZ+(5$WC+hoCAF zY2UfIwR+;~HjR|n5c4_~v`np;sFD)(n%AD!UgkIL`z5I2r6*|zW@!`hH+QE$(c2c+ z4mq8vew~^~Wmk~?yfU}Ci6^s`fdt~sH2e8}{zf3yN?I zM|>sR47)uw*o#bKTnQm^CUvJ9p50Fmnvs?$I%QXsZv3*cA6Aj8;PT3;!#@LPU3cjG z`E}TKqfZe{Et18n<@~ogDRHVJiGv8jb_SL%d#Le)iCcj-QfQXfH=Mcv-y-zTELZ^h zN6j_8g@SqC3k=)BY-byDCr%?HeI8GhY7{=9N$NaT2TChu5=I32q{9iyv2TkxJ)Gr^ zZlqZO=IbfNpMylM>5U5m@hUsFcT96(7)HWzHiXnhZr zwP+s{IIx59Za}E<9#Sf>s&SSuY`gWXz806NJ)KWN=wlXhV^?FjKe-gOYVh47-!%C_gs z=XrUf*Xyom)K$@89klO7aY3OT5Ydv^!bgOUs1!aCW9bvl4Plt7dh6v}S(>wRt!%8N z$(VDb@vhGtX|Eg$+nD3kpPZkS9ll2NWw5~m)M8DFO%V}%ml;O7@?Io>*fok`i|we` z-r@3>g?7y}bKB<6D+8Ma5zFyHimA_X4Nrljb+^6_xFRPNg65zt_=|Q?^Y2ku#_Kzk z$Mo*7)iR?7NQaE&&Nceuz;fUf@JdLwthJ4zyE*?QU7}o4%Ep^3FJJS3=%-Do*-sGe zn;$+ohzJU2eb%K`@;3rnzuI3xD%Q206l8y(ml~Hp8M4$A5OoG!_Lfgwugts_VSP6= zo@)pb%>_J4;=jTSbQO4q*LcV%3e}N{6(ce~vIk)+3(G2fj)&jYK4i!xAvOpPZax-~ z>d#J{Cvcc8HqQId!ZI?x7(OfUi}nKuo=r&3;326ts6mWW^* zPqb;of~OapQ8znSq|riSqjh`lD^eM*_vHju)hJc(U82WS?$w@odIe17Y^PK?a%f#r z>C7Xy%YT0bk51tFTWs-D1;hIW*}|3cdK?%%{IEKDUk+7qyuO-C8IV=cs`Ev3&f@2$ zBo8d#tV=okqT;j zxf?d$Nfj5S$_dzK+uyu9yH*hAWwu&iX&0yE6HsCCjy7@(FUp+UfjB@B@rmCzN~Hvi ze3AH0pUXK9!P}BH5J!rCC|2@Q_Vq z{k6UUnHA~JjpS0UJ*jz4#W25iH9?14VTHwSJM;y?(&u-|{kPC3aO%U$%5_C@`9na~ zhS|ajl`QB)o+K9!giNNmnyQTiePfeGI{HROTITV$Bc)uK&x75&F^+=Q3+2lOBXg&G$aC)S+>3Cz}{VArXlUtdZ(aem!S`$Ab zBH_z^is)uSmgTaAl((QsOeoJ}4EL20h{jfzG1fsy$Ci3hNnPqvru-|tVdVhbf>8;7 zYC-b;;f|7`EL-}~j&7H=y{@*qQrk1l-n$FYz0_$4eqw{@0qxMY2W1M;OOMqKS_egHAVc zo0@`p+6f$qc>()pr;*#nVi3b8GQO}34jOf!HF{sJ8d9g)R9QI+%WSwOF@MprGv5nu zCc#OJRXcqgtX7_DW>!z~wi`CuJ9u@1igIo@$sp+V_H?U^GzZ?N0FQ&slbqb=mI$i@ z61K@SRsNl$;k^6$?X3JnpSyi|lZ0{)$(DcSM(Pe3C;Jw}qn2mSs77R{q3Z-917Gwv3Nyf7YI|x>kP7 zu5|AMr}T@2i@qgH`13<$V#1fVx~ocTG{rZJr?fH9Ow9^*ez9;jRJ+sTPb5dX%RTGt zxiVGb6I(Z=yQu2EB#HFjxMdgY@-d^dX#HMXh(?F&{3&OraDq@}-HIxc5o|ZzJUQp( zF|ogfou_K(@N#1PZ5#hi2XY->i=(xyB;x(^Hj~Kd2H$z#=CiL1WxmFn{=_RWlRByF ztFFpsYwt)v*(BWcjQcmu&MZeBkq8(P23*~hZzLVXyVaDlBW?;ncA)qCmra(Qp5;QhvBFpJY@VCEBM`flsCNHF5{}vD z>JH-FfT`0M>iOyJfaqr@#^IZg!I;?S#blnskmb>?vUlMtClp9;Q6>2EvR4zb|60P^g+-hF7KHbPf_#qvc z!VqHZQpuBvKQf(U%Y7`J#&=cjSy%pjzCc>;*oG zUgZ(7vOG|Hb%3cYTVS1Bt&(g;lx7Ar?_<=LT;XwJ@lA#ZW)!910hWR}$8^=cl!f-8 zV#dv)CzkbiIv05(FI_>1Myu3%OqpAZDbQ>ph+plV$W78xg;i}F=?;#y(6{07I*Jl0 zU2B$k-uB?+&ZWS9o}Kt)oy~EUk}kF;Ki`Zhxr5?M*rlPuFHF{QV1MnriyaikQfLNF;AdvlBV~0o0QW_J?nagxJd^R+LlR>bHk~h~lB!N1! zUfuQL@s$As{u0vmd*L}>osVigY-W0RMgxf7oE%5Zf30~&j-Q*X$9kv2Y5yLJw3jk* zfGueqm)HI|!Z)Kpaa;Atn0wGs=I4EZl7;D{t?%Bd%)K|cf&%uQ6HLu1F+ml(I-)g+ zzsXwg>0LKV8An7_M~1~Ko~qW2pH`FOq0ScHA8#d2i^spcx!yV(ti>V-y-Gv)a)*XW zmeM=Pf$ZJgRnW>?Oq}Z5gu8v0(f}*LF^}fYkEtiK=F}`cuO7S#1#yu3c-Lv3e-f)l zD(Wi$ui|ejFz7w#h+D5kZtp(uxPWgH_*v3-@uJbZ=-QjEUfK{<@33uhpC#VOM+=jf z{jrGQP>M^Gc^ywc|#k_W+YY5qCY-1RMQHsFPa#wwe?Q3r6n}e6goW zX>fzxbVU*YVPrC_bVC zOkahF$X~@~b4y0GTYI-nRS<1(zL3bj=f>EzJHascf~maHV3NL@A(>0viciN+=_)vD8_5BNX$xdH@J1 zx>#(&Y#MiQ*re7afud7ct@P-3ch=zTiNCB^WwW*7^%BhjV$U7=rg8eO@$6< zwxa6~&QYo>hO9UaOY~kcNpl&DA&CrOaww!j&}bapGkf7y^^RmK#JjfKdBW3r{sCr#MOt9g+~!NtS>4(9HpuF+|8qpesI6kpmu0e_QB&Qei>l z5dqoh{r23F6|I?utHrJ&H{MuQJY0J)zkrqMhvwIPN+k1;-dUPO_I@j)yaBvW!iP8? zHh8(6@!1OxWR3QX`SB}-6?r$1e0g+2Q;*&5)n1z%KXpW}v+WED)ZSH^*A~eUSMY0c zAC$Ph&N*_W`-(B5FTLur6`l)*^1!cWWaI;vv9)W>qmc1OPA%KT1B1K$H3hU1! z&UJdlOrk2G#R)R96Ycvv#%iiz-cv*lxf@|I^C@eC#JmkQs~tgJ8LU$V+Wd6P%5 zQsliuExoJU8So`Q(E_F1J&fWLi%L;aSj!BmO$_pRO_)|q0aJHu8XLw)uftj2$%=kw zGN!hgVuqINT8OULhZJ#YG;^#REA^vT#z*gscrc(!Goh%Y_0VL0y`iY{)C$?}?xsF_ z#(&|$hcVLb2{RgMLr^*yvc?4eD6$E10DO{wne*!gI_jg8sbq2pN$d(UuoMrw3RG;7JE2h0cjYK?~~Yq zmmFegcQ#4aOWwTozx=SQTf7TA4l9X<$A)+aiM7D4uTqNf`3aa_?SJ7r-xjb)1Z$C0yntb0zr=0&a z^-WWCUKvlDEv?HK#BQs!;){l|{8drN8C@85fLRa}IZr_#z@$d*hF_LdlkqCd%_YcSP**qT%M@TmZ=tRvnaEPb87{yC%_G0U7En*906nYl*g3T8ux{(z6 z3jbv{1BdEfLRkK;nrY@3sdbYX@XmYG3EV}$Mg@BpFst`wd*yCy z(!Uo;Ks;&B?3c`}VtYgD!NQZr0}(zAe5K=*UdZTYF9-R7pmXGv{gPC4F0rTyVrYAE zS`-Zc>iD{SO+8>>4#w;NE=TxDFj-0z@P2sel=9)6>a*9|b(G*>IxKN1!#zaNv+$YA`F*G6 zkwMhD&~<|P)#~$jO%WF!_{l-G$oDs|VDQ4NJ+B)Ght`!pnZNu=JriXDYe zEM-L#1eR@6d_jrwY;qEe$D8|=+5TpL_xvFN@jf5V>2C1swqwgiIB z+Hh4X)0B~_{^X~EnTKXp^_N>;pBx|P=g3PGLNkKJ{fL&?IQG-B?O>jkn7~$XD>E zPs_J@!xJ{^;v zmY8-U>+Qx6T=K2k%H|Ni$@g*efXP%+zjbN|s>snV^0r@a6v=9FCHP7RiYmK+iFVe> z%Q;i;0>vFw8o}{_8!0&fphzM@*Y2(EF9bp9reD>@{7Y{6>80s&m1@Z2gCCGE$&w+{a#Glr#)3=pXnXs->h2()@3_PF<&zT0$CrO(% zRDOO+^MQA*q)+ZShoxuotg6~Ktll^f`K=>}1ak8Bpd9{i7lAnfi_BgT@D}*nv;vU-38r)!MW}g$v zA!BY1#@ZM`=h~~(9xlnJy>vc&B%&7F!pA4FPo9d)l%}}8f4C4h2*PT*M{2`XirKJM zbFA;;@`XeUBS>$AoZvtGcI(NDYHmD@IAS?Jzog|8`=nCs7^NG4rg0zxSoO*Ekp~cN zMt@?pyCI3I&S*|;XHz%%3#9co#p;XAKsR|d45X|IV%!6468Q%NH8+2mgR7Zw}ZYb?nq|0 zjl+C1gt1Bep03fuxwbdvK1kiS>v}}vN0djmDQ^cq9_!rzOl^5l&bEemsOyD6*lh_m zhE}uJ%ZT(k%Z_P#H(26R(w_;0zDQ>C?Xkp2Je76zctU6kP^K1)lmAquxM4ojjQ0bxPwJh2F(!??l-U$$|FapB7ALhm{U!9d+weVu8BV>RrI7uDP-Avgq zhUb9i$w%ej8>W^N%FUP5Ga03r?MAXekKn-`lnPyjk0)dwJq}XHE2*Ii^7|OW>k(E> zUq>bK{P0=^&+J^7@tQzgn27CXd#c0yFutTws+FUVqO`AEDTzC=ekw$cMI;OO`H%{Y zD~#;ByB3enWdgW9pz%!{HVjFmrB?To2ZN1_Lr)rOKEA_cn8Aj0b*}9E*L%oIJV8O1fQike1aE6>xy~RcS>U2ZZ|%S2=!t%BE&26iM3pQI-$cChUWohT?-E!OgYX{X%?S@Xdbeh z1T$Rb^0j4!h#gB?rZ6|i%W@1RdoHAjcZ)usZ&=<}5|8GQ5U(`cVFf;peSQ&TReg|u{ern(}zi*hd$MGbQ`7U#0s7{en4nEHwzXM-eH zcF`n8?c;4jI-OgndJ{PmM>05|tD5mOhiDX}keF;-Zvx9Z++`laj?r931z+6Vg&n3L8$FXCyZq^~gnB#{)x=O{41 zcgXCrqQRWm!vp8;E0V0=M(nT*CxsQuJ$1=R-sL1+4%(B;48{C$AK3Z!T0_`t_e-nFl ztJguG$7(Z}xL;WCL_HWFIHUWVdXoDf`vedm=TCDTVHc`V&JuThX#bv|+k<1xv2a0? zrvpsTWCDw!Sen#yLo+fw)qx4_EGP$%(m6yw%q1=4$zq~I0l6fI{tjMh{?;AGuy`|u zLpf5F%Q1?#Kc>@Os&Lfz@mcdqYxctg-qEQ~Lds!zo$IP$ve`89U2mQU16@ru0KD;u zN!n(BM~e&jA723N#4lqqfRmBOQL)z+^5y_XBp$}j%*TZ;2R|e=A5z<~cnE{JD@6o4R~Ez(^Dr|Vy=my5Ca|S9Uj!@#?L%yX-4N;s=0eOo~Q{+(@3i; z3z9-i^Rw*TLthAe959!7gO_11PL;9N9qK0@RjzKlKiW$^!}x&s=CVre`VNDyVvS@z z@S8lrN2(n3?whwIIWq251hCqhj_xO49yv}T!=nkjr_0;o0O_g6{vz*UA9+)~?e@xguZeeq`{4N9b5C!v zJ9~eA?W6aCTbYt!IoHZoSG;Hp03bj2{l5TbCi9aX7C0Wa*6yoCwK`Lzi=T_nspJ{6>MVB85eoIDMbo{OyXs=W%#I(~7tjAOjk zAXrh`8o!QC&{`UV$Lxyu^i|V!Nf`?`*ynkmhn+#N-x_b5B==}j?81ztt#myCVL5vR z_r;zMa!BES+-WxP?n+yO6|n`c z7An$!Nf?;g;nuR(mMD8n)n8;>!NaL1!TWj)kdm|mzvtriR#=teGs4HhD&hheT z(?CUK($K5Z_ix6OVqot1xUB4RdGFnJE8n1+u9(I^_;tRCfqE{Ib)q96vCBggfdZ!) z|Dm?TuAg@PBbhXOzlVvAcKfY{3c6x>f#HZA@~u_A;ZOrj+q!ttt_9yd%`F2vPZG<~ zU6HDyqp|E@ht7u=mQz35URguDFvCAO&B44-Sq{EQ>CTtV>O|4#!WS7y^-{uN^4bGk zT~gzAM=YslHNvyu18p z%%xH4V2)w9(-9hGKrJ^8e21Xca_w9kBP2P!aA`T$-^1YS?fV=O>A*XS`{E)~jR((i zyFY0rF(cNn@&%XNYY41GrJJHP6zX>{O^d3u2z+1h=QWOi##thG5RJgfUY;Ai?-0m2 zczR^+vjqP^bdw|1uGZu?zTW1;*F8@C8>p}F-U-E6okc!d#pA(VD|S*EeA;bofK)@P zNG8!yx_tP#?)*L)mdOOleGF@KD6NjNrt{Zo!yJ!H(u!0buyhxRsmrG$yo1Sr`(aHk zMUD-sqB#}vnaUOHYx&!I{ms>?NjKl;_$V`HDR?b@$&TMQd@b^nKp_=xn~3QhM(JWf zkDwsdw^hA`5MB*wh&GNY>eEG2g*)BwFA&)$M5!2Svhz49vx^UGNgqTsEtPf+eWc#1 zINbXfBcjvXSxt@KS7tMCmb|-?P~P#9?c;f$Q_nV$%K5c>@_7VE}Kxt;wF;J4bsCo$u-&Jz(fy2aTPu?9Z2h6yhy!SSOXB;p+x+FfsytBWge8l|!vxOuW6Qeo$ zb_*z(ACsE+9el>Qi}--4c=@;{K4??`57uDiAmaK~&|>$&!_hC7?><{fS<1##!L zur0pngsvB_gs;N+)A~)?qYHhu*xr79_ZKA-Qg~|%{uQtG_H4h^((`)P7;NmB*(rTc zTzPHAN%fNGd83sJ!KS!ma_q7BRyWTmkEmwH(Ea$#9(y%q^)vZ3nY4Lv86mJv1skXt z?Y6pEduCknC51g4K)JJ4I)ZzxsU&w(Wg#r^MW_;~tG#0=e|J@s#^bD_8OqXWyW`%(G|ccdRR_963s!vyFySwyk{cK#d)c zv?r5?6d8!gJhd8DZj;g0iOs{XV}JT!MLb?eP{k8Bhh_J;?anzg<(=iu?d`>&wXs5_ z;du4n@e0A*p!ME^)34XhvI8&A9e4Gb}_3>Roh_!>XqaU^YKp^ zh_(yUb{?fl+|I(pl-b&-?%3AG75<97qX3R&DdLjCDcqK$fCTdHoeLpAc)9mpAVx01 zA7OL1mn{Eo_t@S0AwbZ=rI5l4E8N|!a4oEg!rk3nd!~1GW_D)x_VR)iT>bxYH`3kH z-P6<4V+X_6o%86<3 z)Q2B`y}v!{=}&lM|Net}KI<;Ox%q=$_~t8r`b(Gk>wTYo)lYx(;g9&}`|tPA7u@(( zZ*DyAJ#T&O8{atm=~v&=oc&GpO{Z>jo5N?{V)UcG{P8ic0{bri_cy=V#qYkicbhZM zc;51>FMh8tO#k8a{!>ou?q2g}qsr6Y^v<7O>Y=yt?tl5$~y>joH zw|9TA{G8{0Ab+ptf9!47y!N9%_^ta~{Si00&f%@?JKyz6fBO8_4*ve>w>#In-uJGs zclgP3Z}VCgIsDxhAJ~|!teyP(i8J5(So2nwnR(~q?{d*|TOavdaK``E{jT=KOaJhT z&;E^h#k>6A2RHx8-4FlzxEuWC8;^LzEg$rbx$j)>(=U1H1@E}~_ulfJzy9?5&wb## z-~6F>UFr8%eEZUO-~Ey2?!Nx=o58cb_uH$!`%~9{gc+I}UV1LANZ}yZr|Z2-}&}m-*+ANozH*X-#=0Q^1HwMmp{DW8Grx9 z`>%L|_dM;~{XV_5a5wW#Z+MCG>Gyx~MIV3K*B~KOtv6;?lSL^`o1wdCvoW`#m(3M@w%V6!ViA@(M$i_{iHMez;oYo|3Cif zbFVn_{oh{i@|Qica+?o*@BI&X=#%o7d-UzUec9_hWA+E5XT0lAAG*%d^7pvq>mO8p z$J<|W>NYP~Sl(NEH>5B%o)u6*vEf4r*eB`YzeapW+^Y>SK!6Q#z``tIb=XLJ+_}l&U#6{0M>^HS<|K--7zTdll zdZTx~;>Q<(&r)l3@zuAz=R+>?-|;DRs=b|>X*S9otJAP5C9~4&maKBO+Ovwi+P~@k zuX3UM|NFoHC7-eK|7YL-P%78zrE(KZ!-#-e$l-8F7&b}len$0#+7Rrf-pR46vEpgTP9$iY~eWSoVs#%a52 z_3hwb)>v{nc?NRPCRZEmj0o5fx%I#Z_AFyzXm;SwvfZ&9-I3nQ57xJ`P+p=gu zv(s@0L(@61on516_o2?xsn%+{Ww@TP7X-swoIJU|zn?cLz`X12p6nw~|72M51Od7i z4Ej@3BcFiTa)3%a12v~6<=ar*S}`4S*BV$(AQy3VZO76iCDm#l_<=Q$f8|fPeRkG4 ze+H^~b|+S1X)TsOjwgOAB5Li7z=k?*j(7yZryo(ZDX^wv3jBaTiiAz!U3!dfmm=>4mLj zXyyVid1+yJdv#%@g=mdcb1(+;+1ApTvzyx+t<$ZI*6OL&_BwoBxGkxldVcV%MGV4* z)w4Xy=~($8y~!m3*Bz}6fG8UP`;JSkJ8ho=3O5$E*O%_NMi}H+`>e<`$fh@I6!Z0J z75R|JV&QNL1?)UDXvu9iB?C?`}&ArG@l2^ACQ!z>+v8=yfN@tCbR~A!Qm&8q!(^2pPYs`KMi%^uFkOyaYlQF{ve%-F?ej@CF=? zXTi^eg3Hz}D+piJLhU_wL@o}7Leg)w85W4oOWkIV4kObJU8ZUI&4L?l4^G_nH!$uI>{uW=}D=HH0to_ zXjH8aNmk8|S}Bc4%-2wd+V^S48@B*mMM)Oo#@W%pbX*$(KdxgL7SEquWReS_AoW%TpHptE zZ7yu`=}~$`y^9Iegfi173i(2#RH;?VbhcQkmg*JjL>+#dsKMN^UTs<@YIMTaY}TsH zN^eHwDf*-;P?!J}nvDk3Z4yZ3e7WANRBG0VVhMi3l&4TDl*{mwVvWNDZ&rl~Knj&| zvr>dw;oP%YFV_n-q_zqZzf!Ya1!`50synP@M77YU6{>aG+Dfy~C^Q-b5&2Opm6}cb z0WB+(AxFfJ=0B@=D$R1GSt76rv|6cLEHTiv3WW6;(sHd)uEz1G4tD~MMy=E=hd7G$ zDgz5Y${LG?k6;P*O7|$*cOSxVug<`2z zO4Vv4mTIF>XILwha;cJxD2-nzhia)2BB=wUNDB3GrN(fSs?AzW#esw97tJ}gx(H_AyUGV!ZeFV?CdzZ#`_qtsxq=_QE_YpqtOHIosI)30i^ z*2&tTc*Rd2n|o0Yj|@ zA}3Jk-IonuUC66)tpId0?QIt7U?LMSjAvF20M|-rAdAf=^m-HnAO%ZM3z62!@UvM; z#FNggTCE296Sk$^sMbqiQ>q1!K#?`2RxE0J3Aj_oJsz=Hp$ZqqPH1Sc%FJN723@&` zv^UGJP|GY;0S2dHt)SxxaoqGBpx&%k3Skc`f-csW982|PjcF!`x2(0Vk?W@i4nujp zOn`^qOKfy2*DD}Q{-drN>LO=R?@$zAENB!eOj?Co|#KN(sR0S5P!K`B3(60%t3x?G^5BIt%CFWUH$fq}xDfpp5QES#2#bkO>=U!K# z5Dot2asgyBlOa^1VOFhPtCg73)vPtS)75}a$SmkNkTH`vuA;W2SgRKbl@MIH3o)Qil~Di;M$s|tJz8v@fJoyPt%9IYtWl`z0Hp))l~&B)cN zGz3?RBA}jD1x5^=Y!h59^t4L#a^z`&c@sP>HYu!wVl{M1nc``obA_FXedoSawL}qx z-^)7KDUKHUS(OTCKsoZWz;&oLxt~=k@ev<-MM)>g6i1aZ3qM>h-%?0d=D`w@9sIwGjs5O8I&nV3m0!u0fpM z-i#XFJnkr;92eyg&w)7{5=%^OJvaPOuV>%K5+b8yzmwZDppfNs&0Wh+-v+YWI{bexjtIDdE3S8N+s^Q?#(LG6_Yz3-L6jqA@C!G-8!9 zQgwD=b#W51VtzNz(E7_fTk&&wR?I(DbCQ8#z80$F$f-HWP%%FVDtCC)^dyLwtB1#Z z16@WU#Modg3p;I3S$h)%zOb>l-9Edp*xCR|=h}e@J<99m<}y{>Slc>tcD2=RYd{#& z8}4YAKIuX_>}WWX5omk8b!uyQVM7a(Jpz*jBxq}UYn|?DWNl1V=QuYwgVol?izLz> ziGu#MoY5fM6S^_#TLSJ4tK$v^7Icd)UD646-U-|m1qtf0LV z^A`LAfEaj?-v`4rrUV#`F(rr{QHqeY6q0Clc^Vypw26zB-ysIu3D$a~CeNnh3f74w zighRuo=@Tl@j7I|NS7vpp>TONc$%>`V7pe`V9hz>xg#f*LCK&%pvWoLb~<*~as-H* zE_C4Ax=;uz2U{RjZ`mH$yo8_xEvGB>R~Q97NcM5VujyGv5N`iAeZ%rR*YgdKghAc} z0HlURH+s^gIXBp|ynWlZLJ0eIzi;?^?mnryZ+Z@t(vOs!9Ox0a4|w~w%WJ1@PX>Z; zd+^5AG8+u!^7qrUDlaL9nvFV=oR9jDoX^Fq8av#2z2#dT5y6rJUc-PctqPP5M?27Q znC00cZU@S_D#>SR^6^;>!r-#BafRG^{JCH#OM8;m^1xFIT7e#u`Q_4{jH zYcLEBrXvQ;%!RNIW4%Ha&Hao7y(Mj+S{u(_2G``4B8*!ZojtqX^(@91wS?H>G)*Dx zJ4`Kr)X`oO(~XE?LWM|p5+B&JspQ!!)hy5l+zgXGjX9UVBg_TaC0@M1=7wN+cYgj5xP!Pz1ws4$~1mB^Ag7q+|eN2Z4C<2K6CU7R8 zomI;~Ju52F&k~Q&f=Gi|6QYC(+Yr^u(1@t4)QTuIYcrCG!9JIP5-cc@fl232L)5J^ zkP(%YD2Ye1}Q66U7F z;_4PaowIf;V1nX|+=v3Zz zM?O#FnEC8YF1Bu8=)z=%W#;c|c`mOd&EDjS8+;_I6KWbw@lA;uE8c-z5_)pMap*Cn~8gT;89iVW1mB<+|! zgu0+6P4RUzVIGJk4?Yv~fmD-DNeBEhTT6>D{}UY`S~9;(-&HqmIByhL0}>0(Lzfi` zWfEJ91RIIi*83JT#PBI$&|LIHfEi)%F`wh|5I%>71uB1BSPh>>z_a4k_+y&Di|BR5 z#76kO=`OJRX`NW^RyfD;O8yzCl0Re7XSPYwo$O!_Izj6`0caPA8bydX0NH0V=4FqL zKwCgkcwN9Lmy#7Fq(X}}zve)CWG#Mg`cCoBBos+n}feSBM&;8JjXD{p9F9 zD;xGMVCqoVE(t&bwk5|O(m@~M`%c&0pO|&*OKiARdcd-4b|FPoyICWEcDlW3bqJr~ zR4DTbKp;yjULesrGH>=y+#LotX;XD^iG>wSx!t2|^lWOwUBEK1?+bQ(9|TC3)Qzrn zf+dsqes_fUpb{!3I5lnuVgd{*dOBEn(PCA(%i$}nRLjK7c?z(YijIdId0NTUa6odp z8tf@=uk*?G+eJZ7W z6NCvuwH*jM9q{rK^9mfIV`LSyq_by18|EE2=heD*H2WiqMys5=JGqShSICN^K;)k>9l`8&qe(o` z0gXs|LAVZj!n68Nv>W9Kx*u5`X8S?!0h6XF@j+rvOP6O2+zZK?IdFrz12TXyG_cK~+lYyE@XE;5|mV_Xq(74_{6$0e!B(bjB#@zb8bwZ z$%P8R>oTlqQwFXopb?Uy^gMS!+uH|;>C7u+nm9&5CjeHNI{;=Xsb&0!tQkB#K{qD% zT{*;~dW>YO20LyE8sxZ44fZVy{90%QjrXa*L_uN*8gs55hM zgnM?+X1EmWWQ@4G6w)DE5UFdGlReyopbHU@KGbCZo{{0=w1IzOJrvb!gb*wSVTd#s zLw7hDhEfZi4>3D%!G1NH*BWjj>e85g4r#3V)SLN4A3NyiotS<`uvxzi6F*gW0~x&(@CLZ7^)rg9W} zmgUNt5C%RLZI4bU5AswD{xlc+jB#sG32qTr0tq^skS+oAX#oWvQ zwxq()GM0f&#WtSmyBJuFxjjT|Jd~?ZthkIjl$}OQqce&T_lH5U_p^lC_JfM-Z zO&2L-AqKsF-FMv!MnfV*NpZkfTKz8foqB$N_A@fW5fH4V+shYt2O)Jz@El#Ud4t4T z^BXYEFZBD%ByC!1zARNf`9nAWg*pldP2-Y69tUx(mULU`y%(c07Cnd7<*P=fjwc?zh2Fd>k;$Ry#~T;9NVFa~~5wE-&5ctKsw zUDI}s9-)=GFztneLScO&8VrccV!DnF8njPXnYcMd`eXhX={0Obis7Qel70#)-$8{- zTo7g@V+y@b+;n&;{0gRU*9yYs#DFs6?s~?BcE>tFuCX?88fy9%pagO9KprS4HY0O>#AIS_WN|zUBRAAVU%+TT-Rd+P$XuFjbNrF*g zuJU|Zw>aevhJEWkLhslFwn_RiR`+wl5)KMg7C#D6qXmFQRO3t?Er*EmWX8Mb8VBwO z##<-Q>q1^p228tkZgvcoyWk~qt1kKX6mbdqNj99ZdEO|6^A{{171s6d@s>|F#&T4J zIf4kN)I|3Z)#sM~LiIVKT;p1zDq}`<%R{v>7|%oY5r@h{RU zt`CChgz$hG;{;W+kePvtjATQ;78O(^RhSizyf;>f_4dG(q%v!`V7&K1=P(#1%*_GH ztinwdH~01QA2hV5fVQ}Coi&72%4oX_|0MeqN86+j`4`A3)wNLjMh;3xiaxQ&F~<9d zrc;?bkSuzoS}grG7hE%=X|eHB{Um4`UsRXY(4&QYvh0km6*%r4@(tIKJgetf{+dcSNRpku|}$Op@@JB%a*w!(U=6BK?+EB|#0Mg+mF@F>FB~ zpNZGN$4_N2Hl=Z>A@DpPCMh(NXi9vY=gjRccaG8gKs{=Er;crW_bfU}yOCv^c)(9~ z;*e(dLei3n98u9LAY)RJUjV~&Chu;dF<56O52wz@n>&QwHWeFlkrR@f|kNDm)WCss}J`DYBN zS`|rOs6qjAPl*S}aJ_Bc!ZlcwtVx_xnjL}QFXKQojX_{rQ0N(N&%(;Gxh9RmB!0xz z?e%Cc3S61%$$>c3l118vc*O0P<2aFi{|Co$M5!RH?kGU1F~O>fpRv;gdLtWHK!yk2sR~hbbz=F@s_+emzAwWNd zth#}93uCEACvm11K1rh0I3KwT1;eArvqox=HaA4~D?ED@?76LjR%4gBpgD;0#06-U#uwXfdW)OR zkcDWe>B_NGW_ORgA;K`y29%ELh1(m@!n#8+lElr=G-sm3E%d3yj1LUC&>DE`MC5$h znMmBTO&E;j!WNAWr|3QaDZqH;j@v!B`JQEV@9q$OFyQn%q9n~kv=hW|65~2(oXC^9 zT^qu(QBQ|--CWW*ugN5p;y-ErI8oRD)jqzl<}lrtiBIJNH&Q$8`3C8Z1{gS{-`aBx zVgWoEkXLS{lIp43M{mb^tudeWp6T5*sG|F<&duq=QVaC{*QDMC#aFZ+X9lJf8zwe4ap&_|D4Da=}>-*tmENLZc} zCP|cT>cZaCk6p^gKQc*P)Ig=p84nSiJSp6OI3Q=vhW&#$5ys8|qZf0c%L9*2CsmTW zalW17g>I%aCbJSib9TXt@<0`mx+bVIo|j>_(44@V7irUcam5O(k!KnsN6b%9IYhQb zQH;G?Fm95w8Djw%XQ%GbKgkBCS>G7Z907P{7g`?p##$lHfPlQowCsq8=8{Gva%N}4 zy7$QTtnNB0CfT;-beDyU1&Fx&flJ`Z7ARr=h6d;jmAj_AO)2r1N6bIph~bn3(}*nVgkArj|X&wE|g@qHAjHjdblxB8{tH zns(f5J{jZKO<3uM!QVHB7*On6z2L+i$On2go_?R9TZv$X#nZ|0j~N(-T(4_8zz#pl z$d3cRB)W9da6-t<-)Ghke;&v`?>lQqrT0OPkK#))ZX%dcM*oR+oY3EzCKV*Y@#43! z8@S>hxw#P#x!L;UeUdx_j@;P8+|j`>n~3m$pJ(lwfk_1BL*K&zSXkxh_oIR|Iz=wK zQq34O{kP`r8yurGztR- zuG1HIf>CHT86he!-Iok4X2(FHI(RZ*)iuz&)1XA=@{lc9JKT*h*s;3M*#u4lLZpr* z3Fq&Df<6Y1P&}5u7Y@iNd_QE?gv74mN3&Fz&1 zn7}VBEYm4a!cf5lE8Zd6zkBW$Ou(5&(gaA5bcQY+jXt#u$bZx#-H* zxv{m!TrnBba_OgZNJ7ey56}XJ8K5MU3ga+0v5gcvGnz&btZHp^CdUN?6c;(t)OWp< z#VY839TK~6Y|!S&aIh1h5cJq}Tpxm9etsRd!{yu1gM#Ut6++Hi_pt~wPT~bUvxMTL zRn~ZXj_mhHTk9LMWlB_adGdD(!YH1gD9Kkos%5j@<0v*rQdbq^*oy?G&YI!kVES;B?Rh-11xO$SYi~7q&XuB&-(Dn@&6u3`E_H zhQtmT5`;&LNF4#6$Z;T#u-N8!ajXQiOd32Va5)X}+hed4299WfgrE^g&_#@*>a2zI zZ(^88x}%?z6y^&VqDr)eaQXb0h+_KWn2feDc2KW z7vVc=;_sn2ZUCNlVEf=PcX4uT`(atpu%3?J+%uiHzNfb^o6Mr%5++Fp0qG3gu3+PM z;XF^Co6_A9g_H*o8qy4CUKi&2sJi%0fDhh9pG+}l$?3Qb0QF4qJ_}PeB`Z*2gRIyS z?_nVRm=_2oJKV{ZU#RH>w-oD0ho9RQ*p6d$C76DgmJM_WArZrl>!M3;%*)i?5bqv@ zO9?qfHCqORd`h8andAfhgzCuLl=`P=0WE+j{Zz@Mt-=wLM3umb2EIC%+_9p?f2b|Z z4&_;t>5vUh@+3;0HUhf1Az@C&-OdS9ruj6!8n@Qv=KVd>gQN(?EsQ}*wrn}tr1j#J z&C?Mr%KZ(vFo4p83DgZyF#5x&IMKw*x;jDF9cIbxJ9r)><_Ym}Ha2$qXq?E-g~$;? zuZW*_m}sd3eNbI7(g6~K-VC>7OT@Q(lwdl`OblbiUv`l|JKC|7=VSBZcY$h;Fkn$s z_NW7Nf|1$B)e3?iEu8AS!cx4;ucIS(T{3W{A4XH-h{g6mH{F0TY}5h{3zzxs115Bd zMLM8Zv{5ix14AVMNg*v@Wu4AJggEO|gAs;QgqL2}_eJ!y@lY~-?L%5n!uHRl*O1>H zl4#5rZDUqfK zBf5@a-q;W;#4<*nk8>=+5hA)KXp}fFF{VlM+@|k%!Jk`laUcuOMF)4@ju|SwkH>41 zVV(e(wr4Wjyn^bKzI23T*t$*JeZ<`G!cJpeEHP+kuR z3y(2)q^}@dSRP&J^q1rI_ec;fcQ$X{_;g8C$~r6uf*eZfkCR^xJjfU8aA-A9H?$g zcK{3{N*%_N374j&IYLYh=cFRQs1f7*9#RPx-B?cIA}lY;04eeoNf4#3pL-|oA5l?Z z6IE(aYkB%)YwYmRz?A0ZqG}vzdM*N(Rhc-+JYBQEtP6*U=xtIy0)gj7_R@#zgh)Jh=%74?h9s+-xVJ5Q z*Fsqh<#s=@>fH+G&|ym+Gjh3cOB-rvNhG5e&s7%*)QdKUJ~xIyfqLc!Bx|J>K^dnP zAscxUO0X}{dqae#^|6nmCPvK7eqRpYf#hAUYk7Djc(+SJd}3^muuTGoGxw04GAuAS zI8O(S_uQdpn*qCk82-bf4S3>7_8qxU43mE)zNGm3mf};x(o@aEvE&8g;Rtq-{j^j> z>A2(?dl5sz=tE-i7T5Z_gSFLwq2d^nj57dxp$$ zP84<2C2KKQ6`^H#K|r)C=|Tid{2JN_=GcIuMKe9#PAqeTeIMc{7vl8unnNinL47k| zcMX*a#YVoAFP3`6qijBrh``_zZyN^bhGg$n%fX9EHcvo50?Hs4?>eAAyP&vTci%a0 z0=WB5X23mrcjTeOb2F3N=Yp5@jn!8E68$X_cD5<8eRjukJO;F9LQi%*3xxv7VPt@m zNK}3ys=;Ty(e9XIP-)`n#OOkr@rrnQ!lQ3pX!V(1(fR|ajLE@c(adv6Fy?th5 zZHr8GmsYp$GBugkvIqJIo45TtkQ(Scs^0`Ej#8=SBPi2#5`V>Tj0Owq9sq*A_ zdbLhAqE_R{U7^{N-|nM)ax?6RCUK{CXncK_DjJ5@{~g*+Juy0bgWjKVZKq>*EhosL z`52OySvZSR2E02C9aa6q!u%(6UrJ?S|E0MZIJC{;v)N|i!z*-4aNyk&HE*dY}BCDUv_P|cC`?kZcq0%kSg{S}L#x7yA^|+OVJ1?zlt!!_#TN~TAT_c(f z1N`RsFJ&76X_(=R20NAq5v*{KvZXr}h;-%loS$?-vetb%meuvcQ%v`<2crSo;ssnq zj0ugVw{x8T5_9G~ZHeIr{a&G2>7*;a-{gK%X-8Wz);w+rd&lL`MRj74c|Wpp+El(k zO*siWxmrc@A|l5K#gXoOG;-$^zm4zR6I+*hK|z)XPpSfln0R704k*-)@vnvp;Er_v zvxa~zMFo$fL%KGhF3H#?Yd}(eG3I3%#>zz}8DhH_kM~F(-^+-HF7V}0zP}{P84Lpq zsU04BlK}e$-ChC;jS3z+oIxt%Q$Uu_-~JJK4ThYSiXYa9vqrv!Ec>SUFemGUi!rpB zuRmmjsP*R)6$|iB_9|XHMbxtx;;d)nGK@ClGuGTYE|)w=bl1Mnas-7!6+jP7mAx?y z)h*NOAB1h6nVQUo$GyJD2~f{2z=#}o2eItYtbT4L9OU9B0ac1*dzwtpgfX`y?P9*i z_<=**qLJBWxZ63l2G}0;=>6K!xB%{z%mVySzmFIL73CqNqggt82i4IYTOCJJV33rS{;*8mkqhcft8iSIn$#|QU zWU9ElNdvgH@=d2**Ycg4257Z}@&voKg5-*DEx+Nspw~z9PP1qy^DPNtVrU`?(YQ=c zzJHcCffy0O59Y`NL$Y?7kSt`u5jOFvtq`pQTDR1EQ0N=z^CO$!|O5$tf z1V-$ka`KSH)<&zny|}c|I<>jBaTiXk3Rg+WZ{VB0(*O8L9Jrs2EB;N8QUzpSH+kiu09c}?f;NdYEJnRtNqu^(F zzUAaecH@o=3h$QfvTfPnu)w%zvX;9&xlH0KSa&-o6Eh!sLY8QVkgq-?tBxQX-~lNJ zok>CwGa$&Dma|KOmw3AiYcc*(NLqHyE~G?{!OG}n>5H^(Z#vYG(&CKf#%3AA4b@vT z^qNHah|qUV+kKi8BO&2RVpZSdaf9PX46Q2~ee?OG;}NW=@OU-=79MJu)Jx2fH+?M+ z2uA~{3j>fBX(qh@Q^ytY>))W|4~MIVCKv>$sj@uLt3&wsw3gb^Xpmr8^aRC35157(@$|%O zi0@zZekmds9vhLU5uY&SbWJ$^pZ6;y@s~)(JB*pH168hm(%ncE(`LLai)d9;L1{qReRUL3(3@D_zZwD?Z6n3*skMw zh69N2x0?QY+wHRpi>;0Jtib2wv#`jC#0y{T-|83i^q6)|I=4;# z=EhgYn@FJ|qKyW-h!0?*wpW0=ke&*TMq*(?<;Ms*O&;+Ag3k0^-?vGDY+y1G)*f0N z08C4#f^|rIxt|H6XEAf_Y*wFhDjlrq<|Zp`i7##Q?PlJqT7QcJPPd21N|| zT*_yHKftOJB5q8IEkSaE!u5b70m2PgbPyqh-4KRc`^5Cej-lgVoTpAk3~3wz68E4) zhRBOz3$lwVIC5p_3`EFuYb!Ti0K+1ujD9Trco1JEAqZfO$Tp#W-l-mlFREPDk-)M}V z{NO0s5)kCu!c>Y700%X#Nl_@$DoeFF#Dge&h*~|RiS=c=NEiZUINV?>iTA{99wD-l zkeN6UJh24Xq$sN4h|s_`OFZyhdCFpBIK)DwIbe{3T^LTfO>iXiIHVFsQBjo3z7n7` zYq`R~D`$Sf9zU{;8-L}7FEssy1raD=4k+N-)K2CBOi^I>hgebsV#K|VLx&naw*M9o z+{#Kf#6;6HvS&9$H|#j%wni!kug#NZgwKvx-sBCScdL(X30;3k{Dv&4VrE7C{;G^~TcLV_j?1ENp8EqmSuk|Zr zBgiuOttz<9+-(!y#DCG)9C}St3*!Mk!*mmv1d{}`e6f;_{5ArJ7#^1352@L7UqK~% z$n87O>Y=NM9V9t}~`6!)>vi3_d#dc)>tB2?N-G6&?+A6R= z_$!@kVet#S+0z%40pAS@L%VvW(enr{{?jOB)>p)OAbCKpwLc7QoeG+U056eM^~BKLm}g<7%2vs z55YmiL5si?uq?i?apHtPsIWW+vN63Z+L#Lp#6sr7LSH#B1x$aaWy$|WOE!Dcr(E_1 zo)|-v4;B9axsWm#tV6bvzn4(vdq6{PF?K@fKsp&-7ppL1l;dj2k^rNQtOuzAEQ7YP z2GMKFiQOQ(MHWt$!K*YK8XIfY2wQ=*ow3ST=Dbz| zSZuBBrrNst;Q5!QgFh`NH%77$o01P{fkerK*DogZqhQAqsdG6ZAw@o!H+t%er(l9m zq_6b^f=$Z*eT^!W!dOnI5MG#2f{Z7gx*6IDajxSt#j^PZ-O!k%W;MItmUgByq9VN*EZ;Xl(Ij*@Hc;bGAN?3oiP|pSA)={V<1}|DD?<4}|A%77w zB!I_Ks3(;BpisXl5ug^BJMMPY9zIqe$)B^O&vZL?Cs)NfL>d&+*xL(*K^V!pir}E3 z2|eQ49894i5iklvc~LR!9}~nY)Rusy)&e@Lkmb51Ee1;tswfKyQcqqqish9Vh=!ghSAw49@UU~7;^g7uVK>Fq7MZ3r+PEeqtY;z-EIy7vKFA8g zr5q_uCzN)ZJ_V!}_pzR7=i&-(2qjc8HZ^HlWwM*AtCORTjT6XP>S8y`8k90JZCc3Q zaS z9h}_ZK*7E@l2sFNm~8a2M;NqWW0kF*JX2$3rG_GC?Kqp&TO@mkW0KoEqOh9G)Uo8JOuIVb%yU})_K>;SBwnjXU zfW2jVPz(8O6=ZxYW*0vXtmLD;EMSyS083&xols6GApEcgp#)6S;fh`^PMD1H1u^T7 ztf)@o1cgdH9sBOW5()WyrMXJzy2wsSPXVf!#pH856hTA4GVT%ck)snXNHLUG7&Idq zgDYP`DB^)F5|i(OmmG&HN+D7|B!ULGusHr;WT3|6B!KI}6xmzdX1a@_WV9%-S%^6# zh5(N&l)bP5ju<~CE2wpZP$jMr9|#wFje~{1n}rI2*#YLzRb&(xS*&IV#mNHRY7;piG}3pH5_J_m^fLDFCXH+T!(q2q?WCu1MT@QV)U z2^~0^WMGQH-r`L(Cw1hx3@=R(V$Grh8g2$Q4oy+~U@YADoiO7-Aw^D>Aj>9wp^twkCy1fW3sFeXv&{CqZ2CO@XZ3_!gFT5dVgOo6v$nfz4Y*JOF<~LEW5= zU?_v#Bo{S?HCkC0gF@J96ITjLS#QISXzv9h`(iYz9(E%<_Th(Jz^1(CsX8rf#Fecu zpmn*GN&eI#CeWw4*&+LIBBy|%L|pW4c7R7B`Oq_uI_yCWEH#^0hBQmWL45I8b)<+3 zsDx$$ln4dzZzx!>PrBd_Dv~ao3d`W#C|`-n1(E`aCBklF9I-h*(~rY%xiz#1-WaSx zXbWyyT51XrT`jkSl(6A(_-wp2SgT26N_Rg>tAO+EY-|l7ZiR?QLanJy{ecfXtkVR? zrdz6U%8J9db(C(X!(zHHFlQ>Q5H1w4J;2_WV2Hxd#002P5^z9r=;Jk%W2B(06B~rH zrIjuV026I4=o)OM;p2-{$A`;<$41;m3cJE)n{^EU=e>WeZL~^r_Gk{2&d6#3Z^>-* zB*29ndMDXTQ4mK&21W`z1$N`)2E+NP%aw8gXEa0N1OsPv7*TnFG?wM{6my7lC`=t3%gHE6T^-m0pcPjY@E~&K z+0yY1`>)PQNEJ>*BlfuRj3~D}E)OF^?ie&Q_Opd7Db$xB!F*5%I~>gwOYoUVXqdP^ z29zj^!fQHidgO`CZG^@)ufRBXykq5#rbE3HtAUToFVTxuQlJ=a*>DU1$%I*>L!h~h(UcGs zgquNcD9jp29)OMpzp;?GF+LyDx?7y*5N6|DC!-8UQOKia&mN`M`f+W!iH%3%a^X$} zJrL~&V%ZFV#1V!QH!NhKsIf4pEtcOna@ZK3@FO*vlXFdXQxpaLPPyKL`q<$$Jrbep zUK}yl8ACLnc%;A|Q11dbNSVE@MA z1%prMkc&|)U|gEaTZ%)JYX$?vW0He7k$ys^hz;*00u|s1HZW~L5pZg-(NpLT)lB*B zfDl076H^3`305+^lQ{qn+eT#;Dsj!Mb%-+7{MxQ)dBrQ+Hqn%I=&J_y5i$alC z<|zaXU9YK$4~_(e2Xb5}fJ>b&4A)iaccIARtw%Oc=5f&@ZWQ47*a5awzWG$PR^wHO z`x1gL3a=(l&uGL;ggT#K^gcYwC=nuW4g8p57|hDzfZq1!Ae#(~xq!h}6e1LJn%?Up z4;^UO&8Q+SP(H#vg|1E%Z7jfn7Og{(udYYIDJ0NNXvUf2&xvRhgo>Xj%JbP&37#kA34Y}o32v-x?T&T7~Y0eUV(dYa@)qe%?viC%)zY$ydD5d zPaZcC)6f;wM?~?GO>Taqobi*aD-_A=^jscXrNMg0W9!K`2qS(PBbP#iA<__;MFy_Y z05bT{C?BAjB!NLY5L*p}yj$=c?!Rq$^8~k!EpMK1kkBwkP+ie0O@UxWXR25ilu~+% z7Y2RFk56-s5Zz|HE+K@?3(@;2zj2qByT7R3LXBG1E$Hw-#Yb_3BtWC|=ZIul6fA}g z88%$-4KcXBasXK7I;F zeyBBm3Cmx>uUXupTefEsV^;_=3%iF-UQo$i5=E(V8;J~-ie>k2GLico zLZuwpiUsYgV68!e$Fa9?rNh@Rcs5Oj;zxJ?EOa^uZ*LPQ`lfrR6tvAl{#W3o_$SNbRL?LRnOas5D1KY4C6xj=CbdC7IIqD~r1b{)7 zdDz*|G=tSqD5{)0pzxKMm4IzW%tQ7dy5XQLi3B<6gquDUI0Q~UI8zYw5f9$V02vB> z2i4RzNF?O^TiXT}jvXYG%#`n%zk8j4Qdr5<`ln&{kMdK~b)xkvN3jQ&v{vWI4F(&F9K$lFfi$`R2h`aJ zhX&rZf|T<^b~lZITri0)pqeZ~hDgK_iRC$1@wimWSCLPKfFXQnla{^1rv-ji2x-$9 zYZ8=J(h$7#Fc>;W#C8HJBF}4HpQ4&{VURSz-MLB&Q;A z$`&EmNx*=`VupyNh?!6qt51dBnwl-z)-Xo+zqoxUZ80c-->8u-&;paX?=LgLx?w$Xi?R*j9;%DG(!N&|~fajj|F@jp%9EtU``^wG_%s zZG=Mw%N;~dF-)2vXO>s6Ks7Z-AuTAi*g%&kCY$8Fc@iO>4+L$J=<#dL6~>Xs2~ith z#j(V3!7)~9Dp$H>j#Dbgnr)|z1B-4;TXv5s_KTvUltN~shZS*eBN9O%!n~7>&K4f@ zw=`u?xbWBu{pCA^tgp-+M^S!)iqI`sWUcH#DHQC=W4iG^jn*Lj%0p+FaQ#e~OmhZ{ z!!qX>)0xIxHl4#@n{YWsTvL`IdPzTeqblj|eQ3aCWoBkZqQR%>e`yR;2FZwFY)q#a znV2%nKzS1y!-zztk$%G?MK-r2+HYy=Kkz{eTF@=(4#YeX_(i5`_)B?g@ZVNgL&HHR z5MuY_A=zp<3isrR$C9HEUKXQQ<^a*imTQ45v!3FoqCs&lZxjfJB}L|(lZXw#qBl|S zz}y4S;QbdYj@=aS8UZZ@u#wv)#v;=NWz@-FF@bk5Hht&L1ZaS4>f~RWAOGtQy|wv| z#6%%`Y|`KVv^M`~CZ_28H=)xRCdNjfys?Ri*`M?OcRc9*EMh|#t!4-nDQG6-H2q0s ziAjGx|6>n>=12E4rPGbLY??WpVa)NPnX!!7rfd_s37bLZ7?S?+hek72KL7Ej$^56& z&FCbu3F$XH|L60+)$vD{P|`p2v?BkFX$*Y)jm^M&BL;&(|5N_|j>i@lhsZWyQ-p*m zQ^Yz?gdzX}Uj2xD0evDRPk@NQ6UOiZAc;;T!w7H$l03T59S0C4^U!KA2c1~L z2_YMSzlaT^P9N-xGY^<00aW~2QZS<85Y)DSAqwh3C<2}=N4aZc2x1<>eiceF5%(qo+9XWL=bH zFbA|$3|SE475s{O%u&pQ4kJC#YoHnjRAb5|I>+4*c!P>QFMyi~U^On_Ld0LjM1qEz z9A73PD;C0*d4S#UjKr)Qk@z(T$oMJ{hxZc-8J?T~sG+yvTcbxO@QDaRAE03c#dHA zDWl-bA1;*gfwz+v#DSNN8bGQL%v)q8d?t~|mDm-?ZY zz?LB2LyI5w4u%C_w+73E8&0vYJmaCS|NVS78oOqRJX%Y`4pJ_E#c*V!@d3{u31)T( zQ-qlP@I?Wx1{9uZcNuRFQ6OJOI`hp%EaFgowu)8iEgE zY50=_Y^Le}BuK!Qh=4#Xsm{TfdTTBZbTkQH$ulv1IwW^R=Jno6ub`h zfoh;F*p%Avnf^c>q@k~^gd!0~93m7T8($YRUji%%^hHD`g+6|?=yWtUg`EJZmBjB* zg#<~0g(CDf%*G>E&e)VfhAJ(s$-`~(8>XT_A>)uhraa_+F@QSy(jKwj1v5ir*6ZjZ07P+x268tOw)d`KKf}d&NGpr2Tnt~cVEYU3Nzqd^Xra0&~Hs^={ z6F}_0-Wd10NW`+4|E~6Ax$%EbTk;9Ie;0vh_WIx1qtlrpKqWKcx3m}RgXUCM6ag|= zM%X(msYJ--f;v z13Bo(#d_c;VbEM8BJ=LN>RkN6w-i0qfg z$wkD16yvYl++8^GIL`7A@gykeu;{jW9F&~Rjege&*|M~pqKCRDxc!G%< zBJd~uolnd2-^i41Y~0lTZ~UkK@Ao{*raMm6(&&$@s9JWmQ=GwnW0C)NR0aRq&;GoH zL>d@m2eOGosJz?yZsTn06X(|1lT{|GPM*^%C!*(^-prkIzQ1T#J25t<#BlzHMX4#f z^1buC+cVV`_UkqL@sly9tNcH^y46XXxb)1Ry?x$Bes;+YbF;|4HpaqYQ^mTp+|Lh| zMZNpd%Un9o)+?b-yAx6e%+(+ zQ+D&Vq=yStwncv~@j4}le%je5;zhz#lJW7o>aO&DAs?PuQ{KH7Ta~@Jmo{YIm6YnzK&sXoxxHWA1KCiMqj@!m}>eRQNrO%2Is3SF%=He15%qwa!5L&0Ra{-S^(`sJnf@WlDeY$)JPz4<|nx z^Wxjrn(}K~S-m)4YKxsR#|OS*6`c2sowku0`eJ)j)tKH>YhMq(ICsXbao%x7?nU|| zNgE?RF5=ZaiTPPkyzly!G2)t6S7WXo-v0U;?Ys+VPt4QBea$}|dsS>u6lYB0k4}GE zQe$G$&Gquk6UqC_l1r>bv7Z-o82sg1&Gm^5ZyD5%`P+C^OU3&-SAR;2**c($zd$$o zG^dxb^BapLH0!Qp%|j1W;_Kssw8$#6wMc89d`)?Jt#{eSmzQm?TP$(dl|UbwlGV|# zxHIECb>%_NxZ9JuTYOym>aXGKk!x25O*R+iqB^=gh(YsrfDQ&duCXMQ}97JWGL zDf#T?tv9l5RNc54c7FP#Vx8jxR!aSepgD<`OI?1Lbe<4$FQIR2RF}$oj3W1AvyV*f ze9*)uu3wV>cIubMaqahvaOxL(%VXr8o}rsW+O$pyTyveMzo?aMt9iD+}9odV&8RT{JzL;BhWosnpzu3uB&zGK|+7up}!>Kz>MbZ>W$!cC02=UwiJ zlU^kBDjPNNYp3veT_fxcUQzmv{r22C zHveMJQG)1?7e}qh;qI*qDIMB3w)`F78oaMZy<~}z9p6TU zFZHE&+%PoTB&Abv=a6&5zr8&c6J1{szhv;-rS}dwTq_Oj%II4uE2j)$YGAUW&nToFji*Y z5Bsx1pPyUYx%_?h`@GFH`Cmdbsy|*ftZ{H(75He_qsdPw8{b{r zKj^OdnDIaQSj|{!>~E|Qz`c3GEJHObgtcz!8oNOsKb#*@!_Jdb?5bMu?p>eQab4e+ zm2b&%KXtQgP3ro`+?%$Yw1c#c3?DK$rY5LU&{59VcAMweZc=|8$6uRon4Me^(RuX{ zgX#@|>*nd#ozy6co|tsKzI}R$joPy;_nNb+0W&6cppm~uO)p*^e_*_Q*61PQ2RhpZ zR=*E_@?F2L%EX$B`-2M#ITAnJUoM62F)9^^r584CR$tHlC*={r|_QX@J{=S z??j~M-WhwA|A40bW8>)@@ykmu+r_L1$$wupeY?k&fCTAnx6g^JY3ur4IcpO)16 zbM~aHqk|{S)sIZdaC~vmw^PiInxFH(eNc(X2+P-xy*EDZakyob=?br|Jt+H^-+n&C zKEL`lW191_hQB<0L$MH?d8Te`RkqQ)*zNMyFJ+6mUfUYAzf zg)tjaJg=A=3in=2-~QyqaD z>+&<_4(2-a&6PfmJX68beDCx%GR-eh+&lAA@9OXGV@`Ye^!aO1X3W>XhUZ&`87`g1 zF4CTPjMJyw`$XX-|CcJY>K$$mP4YEVy<77t>E|)KdGRHwq={b&x|*-0+&(dSNyS37 z^ng))rd%TZ#XZQ9uiAciCtDHen1@%Ldf3*5W&MlSS-3o3ROgtL(Q)R}_w(y){7hB6 z9xnV+&iyenA?J@Zoo2_+n;U=gD1Fi)vdYDFzCVxG-#+1yr@hN-Nug`_QoHqAI&~}w zAHnUoB{Z`8ma>;Op8PP)WtwSLT{K7~ZOa|}bL6zolmJSq*3Aaf@eOm@nW^6w9?A8rOviqg| zlro2~%^45=TA*p-CQ?1Ra>ejd2jbo`a=S#QgGnc)3`=5xJ@6N?Zsj*{c-|{`2 zdnYcfUBO-lUbjwhYCoTLZSZN|(2ION=8v31X=K5s`J`b3$$>Vr^N);Ko;r5kn%wEd z^_Mh?(yeZ+8NTQMTe@|${ieu;+9&?vq7t!a>Gq*!3GvxZDqp;pjon*(yBFu#A8BX1 zOZI0EH?pdDnv}E2qI0|ZOWV5+@bL8*p+C6GUygU@dG#pvNZh-1tpB`cKwcN$K9zM? zKlc2Sdq23tuhXR?y|zY`^t^dW?Q;7SX;LlUNB1|)?5FM;?lb%OBDK(U?MFHs4c&Ne zXi!shxI|KChDZ zAh%9GF*$vta002AOR;~_ugihni8S`Q7=5!8sq6E}mt6HbG5DJvxy*fYx4ec`c8M0j zQTdkZC_JD0jLv4$L%G2Zt1Tu?xOcYOf`y`&choZvCagzj5#D?)&+i%^P~%wmWOj_ERxG+-1SaX~PfqnM4^APSs>A zIR3QGKIXpTqaU>Ju!38fu{+auhClxLX1)GKNy?zUu?75#=C=#f&VOE+>sX?B)~%LQ z+<)czjqM9-EBC3luQfPu#U_?~a-sTj!L3U5`0#?Ob6y`zVA!0p8=GCJ`yqqmWbvt2 z>4k#ft~XCy9I;f5w*380l3$0B>s@ZCC0g~V^w*i%J?+^y?i3%*x%ZCm>8<(bfmGML zw0pqDQ|GM9$Gy*~D>9sOj`tvg^mfg9?a});%yCUVwbnOs0c%E|qnmEcQ19tGZFIye z$v*WHniZROf9^-Wv1cJiqVKus)BWJo)dzANzP)u+4PiR@WSQ#5#ta;m!0zBNVB;g_ zW#?{{&TenwcDHiU9kcw@rGIGYM8_^RQs3fx&i&8;FUE=#j<2+9+@0a9@-4;7BRteT zyymP;GMgA#UGO=audY`8sVgh){CGdNeESnU!bXrUYtZrzIvvR^K3KkC#SRt8T{>mg zMju<9UbnRuwdGiHgA#v+FUNguqF11F#kUwL0?tsFssj;KBMJ)8Z zxj5%?LU#Djtor`l_zC?ThI-GA-4q(c<_=AaJ-(&Z_DSp%^~3_Z5k)5AMD=b@XdS}J z?OjYyCNFxB!y=eGY`$9k>j8Gt_ygx;X?7LgHfxKzOaKL9!d_Ytg37`aL!?) z!sGTu@nNY2$KTJtEXXk+osQz(dR4~gu#%PkM+xI&`W}rw>_Hja!XC+Lx8E(e)8BW6 zSJ08S1LsK%le}E`{hUJL4^^$qRE@YDdOJD(sLzXMU8P$sqJF+G^zw?$=`eM9p8E2P zw5mAXCf!u?u*^71mHYiVzxL9YnW z5=*2hx)!#3u{%+tY9@zhdq5K zI?tb%_mw>O=Pl9bMcJzc^@^RcXL|Hvm$|lXRR&hWFM2H~&S&TO#@T8dIg#0+p9&Bf z*SBWh%S$!lcI-(P-$^a2RX0EKU~{J5I`;Ep{nyotOD}#Nc3}r;53S2(+U#BBQ}i8F z+s7Aa4Lf)A^<&ckVXQkg3pMhSGDe!2n=(F=jqDz$vE8mko-UrVajirB`?p)pIr4eW zRrg1oxIE=~+VDMA`W;7cc? zk6sSm-Fxz_vinnh}@`eBa*(qxvN$9I_v39uXcBRv+0XF4G0qUe7+|o&SlYC$4)as^Aemq zNIyOFR?hY$?b>8BO?Ozs$nA3&J?@eo1(v7_SdRjQ$Gi$|#LZT-owo6+-iF7uQu7Mh zq@&!RGaE`>+n=2@d2^+(I+_#{vv?(U#-*8)iaRdSe(-W^AC;^_<8sVTsve17PkCBZ zlIHE!uSj9{(J>X}i2_H>#K%qE4Req2ALY_VA=rr*7_5 z$+9Y4lfgWjJ77*e{fz6tTjN%n4(RYU@c5JfySV^fvxN_NkwL*{f?;4fO;QJbzyGW<)1TK9qv;e``4|G1?#{M_3+Bc)w0 zjMNJ_BwoC7z?iusy%$Css4S~Uzj9-j=%sYjyX>UZo3E%2{XRL`O!~^cPDc{aSw}50 zU9+Z->ssobx)t;IYGuip9qo+5I==Am{EMx}5nrDq(xlZ6LHGKvFt2rdaD7L~$kb<7 z(tKlGemTrps_W%4kq)gv4nh3zC!DS=O)YaASroK==# zA;{9~H_Li^(e_JgqTPY%bj~I;BK@R?k7drM-79xpeR04o zCM0?`>0EVqWO}6fk@DLoRrjqtOyaZN_HgWdc#rcL<4NK1ZYSEmcAw@UEeO>QsP1JP z=oU^h7#BF{?8Sr;Wp7K?#{@=mRA#CdvzORy?9`s!A!BJQ*CF)c$UbMgsJz&G?&GZi zlzv$is)q_b2bN5jq)#e6x?8JJ))3 zt;*F)As2^vE->$N!*|m;ovEB>8gYUwQqYmynSV(QJMgXcPVdhH{<3MrtQ^OSDdfj1 z@D*z5uW>8AJM9)OnyjJ>EJr@ijHp^MuLXnUxGXC#_dM1-nVZM`($&)Ae3HY`@0p(}<+SJqC`v zf4rKWrPU=$SJ`q4*ioGRjkKSy|VX@tGZOb5AA~eK;+tNN0vzkb00T57E^~ zPRp`mR@wH;(H@Y$=F0Gn!}8U;?HDk!YILkIm`|?LG{JOwe`f#uVHVdXXKjwJrAF=B zxw=D4z3AhMydmPFU_-LMf7N9B_wh^j@VVOpHZQYY>a8_1*=L|;yTTQ$cAAA+!>SH{ zT)et8wsN6VEq!=*v&!eMPBD&sbo0H-3|(?WW9ARv**}949O9xiCK(U8Suo1!*dIYV zp3O`=7*Vv6zGIW-#u;lDo;#<;zvH@L=)#>TmpU!=THt>={X2DT-1l3rTvmG;Pq@Ex zH9zrt=ltvHwpTrUqi*|d3!rEC(N+EQ_pj?d#H=5A)nVVt(j5o%7HGYGbnwInX61)8 zwF{f?kxr()(4e#C%-t6Adgtd;3mb+v^o=!|H=#p})K>38m2rlu>gbRz{Z#9}`kv{0 z+L*xD) z#(v?*k$M^FSM66GUP(2LeO$S`c!0-_&Ffd@ne14b`%J62*N)UM1NXQJPS^OP_U|r^ zI(__Uhd{n7DS2Xe)xell8*>x3N*U|!UhH)L6se@0-C5O73oE>{P17moXqG3cGfpn& zbc-&@jChp!G-w;&KW`Xi)yr@7OP-&uIos36K7Z~J9#BdAt*R?HZ--VMlZ5Q)8uEwt zvnBd#R%?;_4fM!4cQmecdCFdWs_*?nma8Tj6{@A_lqBn1u})m|bK~i6XAhQbd$No4 z?Z@Eop^ry!R^5&rWK%~st`&T_ zpk=iprQUJ#iFch}4RSg)pQK~IB&NL9{rq;lllmmXCHo72x%vXDBZ57E?$x!6)#$z_Gq+0X)Lu)TO#QxZTm8pZ+qXWQQrbJ# z=-U|YZS8-Q4%HWqI9TQT=u=YY_4s$*iJuN%a$o~-v)g2)`IUNUm#C5*1McZe+*e&=2nk0CvB^DFiDhvinCFdJ=UI@4^H zS!K`dMf`TVe$<-3*c4G;8*P~P<&3FzpY#rTBrC+Uf7eU+fvUFtl%`&6Y~o>!NjbB0 zgP#BK**)~>fSdYO9>Jg9GAJx(e^)eET@9{JO9{-Ok}b zyFRg$+hrB|wkNB9tsbONcHl{lx<>lBTPK7mvwF@B^7U9hI61*rf8*ix&##x1M!sCX zvF>}~N)wlXIi%qhF+aw~Fu$9f{;FlTJo&_xQ^UT@B27CQsP@|WUjOXO`rKMQdyON3 z+5EM zGT`9EzfP!ZryJW-YsCF6?C+0j%XgowxhkChJYlL}T6A*WBh@tvhvn}p{g`#3TxErL z=l$cKk_K)VI(&z)Qorln-o_geKOGa^A5gJRKf1j3RGzeBCF9f3Ua^#K2X1bhGq<9A z;?FTA)ltTK>Jp9|RIjd@y&%rrIkccK`Ju!lYh0jJXq{F~-lDC?CmybU^W$(~UZ8E{r>p$^%e9)5=TdhM=l2_I;*K{2?fV?c}ZeP~^aUbmFH~1d-G5q_X zA7*1hPHn%dxx}4x>%)xgPrhCYpWjgWA~Ju$gP=LV7f-o&T6A zUUy=?eOTc%G5S-tJN{djc!Fv+YVY<<{9f_t%{`ZPR4>DYqvsY?o^Vrf?sO*{%v!-g z&+ohs1LsY+p0KZKr&H(!wUFp1->bqSgX`-y%_4=&P01@Dg`Qc} z>q@2A-MnGn*WtT^?xhZXCzSd07 z^E1EBDM;!OzNda^s{XjZm9-DwM2<=*8n~iXP#vMMhJGY6wNqfeNvQwet`A8GKW^<^ zG0*FA?vMSKbA&&hQzrG91LA8s&)9Ivp8hF)QTE^1yAnXCzAiqNBw3?u zg*I*C%|25pTcilt+8JXoVuqQqhq0uMR!L;3R9ZwumNqIWsfc7PTD4dzl_-68jESM> z>uZ_+_xJq&mHX~{@7{av@0@ebIrpCZFtebMm)7uKMbtRJy>s4de^pgPvksd0MgLWr z=h?fOo2*}NofjZG?k##k%_{5Mn03DH$49gUR-wNm{;4%b<37^oh4TcR}zwJWeQ5X`Dh`=F{I1B+oq3{?S2?>71Asy^dB!|DX z{|CYh?f>}`*P!(O-TQyw2oM2+X49Xw{RfQ%^^piPYAF8WPh9`){vSX}umgzoCwmVN zdvngLZ*TR1fB*PTS61vc*@4`hZJM()g>yhMh}I@i`t1ek`xtu*47+?u*J11SJ?C)u zY>(;MUBqrE_Zz#O0|DR$1lRt%kKh;#n1)C!9t+Tb#o^f14e9I`b;ZqrN}O@&8Wa=; zgT~-67zhD|2P4w++V73eFWU*>R3s zJJAg3+C)|yb6;ZOaUci{g@X_{91aByPRw7oukDu+6NN>hacDS%K;qCCEEe&7@*t5A zgu?+QjRukTXgC5h0M(IrKtl|8BN~s#;_=*sgnALOi-bmG3h6JA5DtO<<3TVnngvJH z;3VWxJfaamFrwjD2#dzy5d-28k3c~<6dZxY;}Bq9#ejH314=@8JO)g33>H2h9{(l~ z;Sdf&!Qo(&Fb-@B{{gPbgFr+efMSJ0WAJDeOTi-%15y!*$H1{zpoy`d20@4csR+Tr z+yH0<%oPXFI|#)=cL3RO5E6-J*Zn5NbqOGG5B+b^8-r%C7jOa)5T)=Rpf_)#7L7;3 zQQ(KA4Zxv+r6}0$j>RB=CXPg6;kbdNC>)%ji3hL6V~{}b;=W5m6AFn3q$nJRK;h6R zJP@K7EczcRMUfB!j|I{ZuvNr>q7jM2-~c-Xz=Z=12Z9eGMZvtlfJfkXfOUvliuO~7 z{eq{1V-a9U=l+Eq7s3{;DFwY zgad(!!wrbazX?Y$=`i4bpcSHke*X`JBS02}1UwMJ!_inQZXh_~0X4vu764j21T>?8 z;D}<)ZZrge(ZQmieo8I(ITL@0d}t&Js6lw}9}xFH0z#f7A8U6XSSSO`;2`{f8BiQz5f~)UIN^Af`imNnfIv~eV(@qb5H4^)!S9Phf{V*vqFx_^&_9!U zD9{T45C{i!8X(EPJI?H6f}Hd`e+w`e9N_k>1u)=uIQTzw$dOT)0|Evt z@&Q`}wSayIR%inXEHE2!C@cyA*dsvCK>Fj~R2;D^3ls*0#R82OkNg3d#hZ#Fga^by zu@~7%Z??cZpy$YT$(&3aH2kEZ{%D435JLEV+OQ9EAf0aF&uefaYlKM5X;b@}XIB z8xB|z0{tVd37#Y$9zvo38wK3}n*Tt?P1aO};CP^A0#gYNJAgXD&Vo&*f;OwLPImii z1j2zVh(NPIV1bVW@dLJMUL+99Y6+wquv@aU@B!TofGjL4CkBoNtC~Sndn5)6mU~DP z4y*+R5_DARUxgSBjRgV%v=Qj{|4=(Mie-KTn`O2FOb=*Ai%?&dkC-b8#l?&_(uNzU;rez|*~|n^NiIp6cw{kUFqC1D`efnclT~9rlQT zIF&)T2D>ij#hmQ(U*li&39NK8gW3RCfiT@UcM(0`obTa6qG?kcT}bo=WD28qL)pE8 z{Cg3uud9UD2-n9Q^R4cS1SW_6^y|4d2ptjX=)?F7^YEdUsEN z;p3N)6h0VD@!uyZxBY_Wh}y?S^m~cQOC*K=JW2Z);dzaueH~I%NZ={rx`4_e#di!x(KqS+*aRc=O3iR_p;pz_00fpOC@Ou{oa2}FY z_@5V6eda%}QIy*Z`-@da5MH8=LH?-f2wM7!^+wE3Nlxxs;{qnmi@I4lYcCS*5L150$)4nGti zX4hQ=mU21v?8HiN+&T?ytmfa?II(Z480VV5NhjdVOu*5_V?(KBam-Ab$s$*p(c~oHt*yl z3Z(a01$HMAD|Ef#%U`-dO@58KV)}Me~9o41~K}13Sn?1v1MRoB2 z6a-PlG%#EG*X%-uA%&AP5DXe;dI0aBC&|*P#}(#Cq0&g~+*)*yR0m|DqA@_iE%vx` zYO(X|5GV`-!=A2?K`%I8FeedQ>0fV=SY#vFvpsKYO0jxQ;&4b~GKK8w;o9{rbKt4u zm?pJ77*tCuV}iFHxhth1sf$T--|E63P-*a7X%Sg@wp>B0Y4mRl2535GqI4DaEA3Op zL4R-j_6ybLLHzcynEgxeJ6JA}A^Z;ES4FjNl_!P0}*VC9K!DqepOWaY&hb{^)I(&`QM^H!-@8A zj6H;eNWk+C5l}>e9RgvG!()j=2O`cMH-z6I{Hmz-+v?7P_~mxy{afr;L--xS?+@XZ zYc~`x%3tm<+<%Mw9kO2y;rB=I%e@JXC-KV_miTXxzeD&P!tW2^m+Rz2UbJ6v?=u|2 z?+|{6@Y{o5?(@TWQT}p=BM;$s2){%4W#JbI@Y^p{Ko@@X$u10@MlY+8T$O-dg){M? zJ{O7mSI)!-t?NAy{f@xlxq;{tM*15Eg8Kv#o*M}6_0(^i5!~lD^4>u732gd(1HpZC zFYiHv+r#`Dp}}>~84nIbpHP|KI1s&0ROCfw$>p>BjnLrE5x{%mlY8;{8<{2d>Cn74 zBe=a_zi}Y?oW#a+1HtW1_>Iu$b6!8s4Fq>w|8JQQVAGb0YX1XFMOga%A3ieqzuJSw zSsi~n=^tn3s|Dz7PkUXu)^;>d0LH89PrI%^317F4v6@JsgIt81L&=s9C|yh6?q!M_ zmC?NnLPFgyrLG~9z*4HG5@$IMe(7lf^fhfD9jqYPE4-Pkc6GhPgU+D3e)E<&gGQn_ zG62CjB|2mRy=#{+y1RhAgya;XIHe>4&D4cJ=~~GldbPDbb@XTiuLT4bDrYeUs7&iu zjkE9v`4GFh!)~{W3!r_M4FD8;H~AvF*$jF=Ljh+I=?31yWs+czAk$Rn@59pp#f`xb6Af9 zmX)i=0ggi11D*;efDP0RtPhePJQ3u3BXU>|(!bZakG;OB>q4N@4ed!32HAnkvI4Ma zMDDVv^qvD>a|D=@y-6-~QxeURLS}T0L5~SWpTM$nfkN&_dN9VGLAdT6!5A=doQ{%6 z6l0d1sP{L!3arWY3?~yBJ5vGYj;WImoeaLnZUeG=zp0Ch9f7!p{W3jMV7kF$*iZxX z^KfC9yV1xD64wp3(XMZoxMo@ZY+O7!%eeBX{cp1j2qG}6Kyu#SJIOfq%6>!9TV_D& zKt8e}A(5al+`rm!|Jox?mJoc1fQMNSXpSTXr}HKh7T@eL%jr6?#EU&gRG6}uCY=FV zr%P~QTTj_H*uS#3Gjn7LyKk&{;xnJ(NT!g~Ee+LKte7>@1~h`36PY-p@0aF#f|slH zc}lJOJKa266Rr-ie$-UJ(=4W_Ng;VKXapBE6;ls87cy}X$;X1ahD6b%fVZ28gdS16fJ!6#f|d|mSO#GS7phlJZ7`U? z;QXyStaEP7-3Y{9GLQY*Dd2=TR}qmZT%hltvw;^u&z*H+2=qgs9|HZ4fgS>w=K_4c z?0!5c<+<}S4gr1$@I!$AF~Cm&1K&@|_f3}kFIYeVdlQStA)w!jdbXQ=2?-F0PO|St z%-5mQaiJ2|fKg-jmEBrS@lRVedK0!^-gBN*-}+>(|DOmu#Lb7e`M-#pBfibe!Lhi& zzX<}*kQlHe;z`g$;1D!mv1BET2etnL!hX7{+6+JDtORx}j`XmKFYADlVO zo899!uK!ovLm_}y9S$DFVGtmGkSDvxZB_WMx`zj-!+~76SbWzB7ChO#K5F)lbq~qv z9+DN)g#`-?@Gs!ALUebJSGv`ES@?tgd1ODIa<`Kje1b;k-^4CoHYYl{$Jy8AFazS) zp{q?$@344!mkj+n9GsZ#?oeZP<(}eiy{NmYjt6lKiNSKSv-{8^tbh=*wG4)6;3&#*VFU~SAeSlMh6zB3RL0 zAczKwK=I<_5*00Vt^NX@=_19GTXRX4( zfmNIp@QuL&K91;L0gVSQ06#340m${Qz%8cwy`di~@&CzO0N5Xz_XEy)c8&fszW=@c zv%RNE|C!!3XE@n^{>!<}J-$52Xt`IrgUy&NfvJ#ViJGkLJo$1Hz2bpfKafXzXroUgh6)UpS3L=EKfjvur(At zg#SNrX|te5*I+yOHMBhmU^M1?vk|Sq-dXc?vyQr37vNz-*ZSweV9Nap**RtFHV8hK48SUlk5)o0*unfTQOa%GO7gkkd`kqA zN=srtE75gPd`g#$#aoSq`7Vr|WU7T{bf9HRlL{!~7R8?#mVc?y{*ff#6kVU15WQUY zn#J$crQ8sa1)2*4#}+i&H(wf&SWBxi+J0i6>{}nqxUB;B9l@)OMEK(8KXHHLV|{J< z4(7JKx5_hxaf(%+mDKp(KVqD*no#HB^uZ4@V$QPnd3OAs;B$qeQ%=2PdVN-#5+Y+N ze!;iaa>?ENxL{Z96v$oGNvdwRXwAm<(R=}#DfQC(wJ!>Ws_dI$<0(NG3<@{whY(_Puo<(}Wk` zXICV^^pXu)8%2flUpEA}OBJVg#ujc>EiF-W=clP#)SR{2$UnV({?X|bakj4}ry=X? z;MVDP@q53>C~qXLf2F8wlpFi;l81mL(&)VXybG23OATs*Y2lCk3ziD&@rk#Rw;!m8 zb~aMyGeM2Pj(1-;kIoW3?2&nK+$vtHAjB@WP z6*eVNYvHpK)+WcT`QS)$yw*NuZ5k zPOTqN$^5>|_~|OOPK94A)$EVl*~st=GNp9jr%1!KJTDiS3J3z8PoY-_wG5@UVnLLERqNqWGTlE3 z?ZW5#QTO?=`FUfur8=woqXQnI)2oaePFKErH|6221JAT%OxG%7V~wd)pTj5g6Q*of zVcLFJMCS9m_RVN{yQry&^IGq2svlw65iqYjKq)m1sa0|@J6n~4HYSG6XI8bxI`Ge2 zKZ3dBz32)-?>dIrWd5AQSI>33{{2p3fdoEVzFDUe1}?+-i}g){RPu+&BuN~H-dVu@pU>xk(M~C(tG{X+MGxH#Mkh)Krp)+XU6&3`r z;mNJrh-OcwcLngVTV%?DWmjN3yO{(}V63N6-B_{Hpd1u_H^{F$YpBzzeaxds5!TXPV zk9DdPmB8R;0p0I{bINo*7*q#`E~nkMr*^+5^ymy77r@TIGn^te(fU+Ri#0`((W-*dWp#+U8PMQ}>ZU4gIFiPenmf$okN?)6f0Hxkjq zg+N;X_`VB^_xEm-IgQBfaZiz@l{u+*8<$(t$P7-`IeVjeMxJY{6dH+S?M+2@ZZI%zXl`nAMyX8p}zh<5ECO|p zQ>nWW{znVI(8a6Q;Q0UD{yzj7Y|icL|AQlk;(z|g^#lGt6mLuap$X%R3)AXG8dg@bIDt+L0O#xYfsDY6<>0 zP5;0rH&?{!uhltq43`o2l;3@ao^=LlPcdfMk|zoqN(zEsl$4xZF~h6u)4fLzrcNqo zS~cfQ!{fpOJ2AfFuZ@&q-c_!VYT8*UF}+l=T;B;{Cc}?45Y?7?OsT4hS*#j+b+v>v zp{!|J#g&uWXKJR$M#+So6={50zP-78i_6|QA8Tja))c;tgw4><+#B!X*7~M#tnB`| zSNM#wj6&z+GjBmwyH}r_#*D}^_4JZ*m8cZTuYg=Eu zcv@AGl7aJo-}+Hdt615;<4m!v9-f#^8~@~0n3&`kK8a=X9x}>Z!qzZ02`x(8wMR`b zeRaKyvHdjth|+^8#%B%Ek3>R?yrM4VpqI_cIqB*9GUM*SXOdx#E1Fegx6gf}W zFyyvWJIPq6Og~F+-?Uc7qsDbjg|5k8j#LIDnQ6u~G~JX;)HwYx^H3Af>}-8($_kM? zd#=B9R893PxXtwO;I*u90@Ok(G5hK<$y z0$!SZGx&h|=*y9jam&VM2~d%1io(sLb5|V2Gg*7~*|+=bm@oY20nwM-xpX|OS&TFp&fjOen%d#MCd%q?17Qfx%^ zLf2rkiH1g=0!bU{%io_AIRZ&Ty@}Y%wPv zHnwEXTd!Gkr5D1g*Bu}iofo0nYZKKK$T{)p|s*u1tf{;80z1&pExW5{8B%9jt^PXoC!BouWiE;i z;?F*5q5KS2e+7?Bu7*`FC_Jf}73({^GCm+QRF;yE@}*s;|xkBGqerGz-wB2Y$|eMR9Ep=cTd`H727WF_h=_I(Cb6qzWh43 zCcY{6vkP9Xn0L(n{2r=tY~fpp>k9Ly6@Rk7N8E2&xH|G3E!n)|v(Q|fTA7lwqS2*0 zYeenl28(F6Ka46JrkK4;TDEM}j=BR698fo8X1}VPA+*F#6kY%HOTgQ+){9s9zI(Nz z)%z4pm=E{%My&MtYjI#&?BA|rWEU{QR4#qvGNW=ge2FsGW#-0?n1xMvj0_%xm ziZ<}y(>Ps{sXt3qR1WL=R0sYLg+wCkHy7R43`a+`R%eDj~kBP&xeZ75qvc z7T7MGXO*jAp>BFZeL39Y$Q^ou{^OUfHY+C$kB$DN24Flinsu8X%2{!Yd5g&bR;@R-ZHH-vZB?A+^SsNCo z#?{^v?zqo4rE|Mt>UxrEyx~+Q-{oSC&d*#9Qnt_!GoGA(vkLz}ux<3?NYve^7R^cG zVOq1^s9v4;_IchbqeIzpQR5K;nH98R#FmkprwoJXZMuGk`r+b;_p;2LItkQ3X4Xja zI+3R*wvFB7KK4ajLtRIMR7-FbZaA&jVE3`Mdgk=U>r=|k$UO2@TIUlxE@$=CbGw!o zw7m!;EPWD;*y4Fcd)+f1XkAR2bh%(e7wzJnLNdneUw zCj&`Zt)w7}AD6vXE#<)l_qXK{rw`jGw9kpEE)d6ccA~>lulwAbV9DekF$b1ESyFnV zRJ&c=yP2iC-rIL>+Vnx&=}hRf_l@e~3|@=BYX~1can!mDx(b56{D6}A3SCn>E4Asn zqQh+T)-2GLW*l1drJefa+%>n8lvF<4>pYuJ9(mhk^~+7_^r?CZZ3j=tC0{R)2%Y#~ zbXsP@=n6qT)tQ(llinQJSq+PqyEZxK1Q{n9HZf=f;#DLw3ucJ5w7MDNesuVq<8owe zGO-9{9JRai3HFPhe#Ysv!xNpJywW{idsPNecWs==7h%b=6Tyo|4pW4ES}$iIEG%%? zY~H$0b^A*Tnm^X*H8F(cg_Rfi%33@-uKvV0aMbl&|HY$7)mLsMrm5UXb+}s%sN^Dyio3nYFb1C z4hq>PmZOL-ke(M2)2P?@M%p^2I3o7mhf^awAMmX;vC+szx_dUW19#VWzG=L|C!!L05t|4?u)erduAN4az3CDaetl{v)+ndrCdar#$WCwAEG{jl7bDwpy0! zPBuyCe7-g0+y=juu-1cTHU?;3lRi>ufmZT+mGO)`?7)(YBibhyPR0=yEEo$z^ZPB> zx+=o-+Ou`G&)d@#RPQM6lZr4Fot3rZjCzXyt2>@_Q>>reQ$kY9(@kS1$6lE{W<{0Y z%S@#YK^lo+VAk1R@Qua_15H=kJ-B{ zSWC5gyq1{lz3^SH6=oV=c`b*G;YaPNkD9h1#3TOs<%zc>2vbk5#NIug983$DyJKpy zd)}eV@+UV$HkK926;B>_UU=mD5V>8`9E@Uv{XdFtPArf;ubN0$e~JieZP}T+)Utpu zR$^I5w$z1yb0=apRR=$1l56Z;i{`#mTWqHh@j+lI{pGM-hVMLfS{RK_pDlfPn0PI0 zmj{`Qit-kA{9HlEem&JYTy%8mV&q~o%&z+oHEM1!Dt_6q?VomT-1Bhu{H-6)b?6VPKPA{s(K@A6xik3rYdP}q_o4gMVy;I! z*qttW>kzi2dS0#CEr$CfeE=ED7TWtF+p_9;I- zAiE&o;Ul6%{HHgqhsSIgbsc@?8Rfo$$^5qG9h0+L7l&N1-!C!ddFo~xl{a!Eaw~Qh zE(fERcOkF2VyQh@x5UEzshv_RW_`o@>**9@8=7de)VNse;|&ox%T!4+VR>WMA-3d} z-@6z)d${du@udO8+Lv3*OQ85l2sR@r%ksE&xY*eIm|Vx~iH3p}qVD^~9>@BQXq_o^ zGHI*tsnOGgppQjGF-2v>yAAOnA7dhCsLGR~RiiRua_$?=q?n#^{u~nK9&x+P^_JN+ ztZ#=mBQAe9tg=TdR^Mj(wxi?Hj)XqA z7J*GUmsjis!#U?HT&weX!MyEy=4&N4?izoxPN7f*LMECKbVHF#!xoBUB5p&cCwz85 zFPmI=%0Sy|+J2Pd82_b)=*-1BB*75%Z*6O0dC zwGqHe*2Wq{JU%e(b=sx-ns1G6MOI3PdcxmMSa$TWdx1mq+>i`cD0Kq1>7Ylw&irjw z)h-z-uygGh8K+l1eM2!Ils>p0o@rlv&o9!qdeqVF?>b2{l%gM`EdF$Dq>qex&S;Z$ z7cN~6STG}Q6f8mK^4x37%9;fg#YZ(9^G;F<+wfi*8fmI)U;6O4L5Y|`mQiv3B5ObM zD@&Fvi+O%dyC77&HT8jsc*~CFn)9bUX`T1fWd&`YCtY-jB?gx)X!mv>wkQW77Bf~z zdn&H&=894GvrYV(CLPU&4jJbwXB*A`oUN`~G0s?f^oF<(Or@|@b;_pJ(^|#+B||{@DtObC2R3T zhwHo6#9b0i+9BV8O?tN`eo6A%$i}BO?Z@}D_&X@veR}=1MP1DYFF#cNm6)qBzIiwH z5L@wqS3Ww}wh*`ZCki`dTzITyw98X)+W4%P2r;?Be9_Z()6gN#gu+!Jt13koqB;#F zoUB8qZL=Wn@+92cpjN@R;#umA?L{AJ=^f7vBrn4?96jT0>2H=EFpp0v*D%H9!FMD? zlj1L!3y-`R=0_E2nx3qd2Mao7@+qKcw%mwN9kUdKlBbT!mh}(O9jlj)cq@DNToKl| zQf}Gun|ksA#a9lQb(}UKzqnIu=N{(~G``vv@v*%+)yD4$I%@Xx&HTBKL&qA7{A?fb zYRTf8^t!fBQWGaK$EtCB$~&Afh3r-giPd2BtF zbnrAu8NUytYFiX-(iy{6xJqepY z_PAd3xLV*0Z0dq_YUqQ*E@x$GmeA%2t#jB~5ba(lduW|^{Vl8HJMO5E9m_VleRPy8 zD16a0-S?6D9@#cAEI~lo)^S3b3T)wIc{<&4Qf|zo_xSkuF@ADc5vPn>uLkE{Rf@Rp zXeyKRpzO%w2Msh8X6+P-wu4>MZLNFe zPChV6RR1;wJ$1v2ntc<5lp>!Q>7FREzIk}2v+B}Mm!~}1V0|}8vV8e5DG7AerL(T< z1azuNyr%<@OSi)F@(VB9{ z#UGj;%yxezc#J}t3zI%ST-M-%K*%-|=`a~Po$c|eH-~iySiCF@gW0LC=ex4=i(Pi}lnNA_GQ#WQhkqq>dXgA41?t&Adr1oqYY4=(; zX7^R!*A->+y+&`2UrX8R|FQGhF4<()e9PO-%&>JOUggq4!TZM4*LGx9RBWwLSnf1T z_Ykvj(JW8#o$!3i_RUAyd>vM6?YMTMq*2hRMCPGegz_a7f!GK&?RoQ!9`TJ(ir!>1 zNuiPFXh ztPjA7Urxu=Xj+^B``^HfXTV~6qS)~u^s_wf|PQ85-wKVErt#6R_K?A17 zC%opGk0+iAL*Y{JOJA zannbcqjo+FzXh9Br8VzH(Tf!y_{}2t#Ep4*edcFW3n7%5qBl1xUnvOBeT;~igj;_Ookg!gYF^bd!kyzwy1H0lw6vV; za4m{OqT02jwk;OX?xL_@*lXgfiDTLu({^arT#Y~3u)qc`fA7=M<~`}tBF64%yqm{7CKmU{4xGm3&*85S1xzP z9z77%5M?K)xXw_nWO>?4dDCEvDA(dy4S9(T3dDTh?d19LAuhrd#kY#kT1 zz(NjXR}=Go&S8(-J(%@Lhx89l)}Eb*URbz6;?SMEj#+ zwNR2wA}=&KFz4kQH|a;F2xYYcvKe;{+eJ+gY1voTtfBl8&bSA&|2#MNO?m9?31ZB7 zTB$c$)(!s>-}v;H6iGtd-Th0^wlM-!_>N=?wG#UY)OVrmH<_Qtw=DCIBjo;-4Gp$2L=Jvl4A;@INAx;O3`fdr!! zlW5K8yOBFyMcfJ5v*LvQWz7jiOJ=~=2{uh#lW|=Bj7EhWX0GD-G1OubI%HkP3aUP9%UG$1AA(`M^SJ6EH0GGKYt=jyb9(y@6J+_{am^5it3}L^$#3QKU{vyWCd&~CA)<`tw0)%LB=B{aXMKf%gX8Hv1u58ndrIuSCnc-MZA#OP?@Q7>p$$BRa9Kv z6@X_LB)Ge~26r8byF+ld;O=h0-Q5WU3-IG^!6Cte6WraQw5#o^t+o%f)n>oVJlr#P zoqOgU`S!Qr!<8sgNE?%(fRn^`?oHmMjv5~>3P#O+Bvxw=OBH1v_equdDGDv;YgJXs zhM0>paPaKA{L51WM(_zU7y8Juw{b+1QTq~%5=%(4GwX=PFB5Q61-QQv+J%4SD*o7D zE2(x+K75)O&1kY^BGn1YYZY7%F77h&XyR?B=t=drq3` zklkTT&Fpr4D9F(bjx`t{vzkSYEaeP2^O-m5O13q?+5=Va8<=^EY?+vLteP+AEt zG;CNWUB4Ep9`|Ho_-E_wI6qx$cidz!I)U-(WRi~ootb*kM@#6*;=^Sd6nxxR5#(%PKLl9@nBik{2++(T|tEx)jP6oQsS?rcW8jIbN~5(|x7I$KxbcRHpaD&=-mhA{Z>NVu zzW>5%z(fES!6O83tI&^PnRX5?H{y!Jyhp$o6`AL&hIIoez;CUaaV4-B6fM z^bLLeh<>pOC)e2ze5cFX&~R4#ZS~~M5Z02K_X8zEAd=GCLs&j?9Yha$EYy7T2KruF zY*^7=eYyAkEw%+5h^w=~`3R75mUu~MQp;U<@9`Nj$8enYx*8MOFG5;dKkS?jZFUCN z83g$M2!BB)tb1n8v!*&hzn1`$%rQMk%4kvUdZO&4qNVsybn_oMFjCKXT?W(|u z2kRweGNaEO)XWqto~3KYsKjD>9Tr|Xw^OOs*tc|lT1nz zB#N>R=Jq|Dm%n~p27kJ8%R07On#JA3o`P6~U2!n~@CyhHEjI6+TX6qJ949AZ;YrcK z{jvPBPP8dLB2)kT4B1Fa8ZJ{q-tW9L^f=^~^kZ9fI^EOhpxIu*UFofk+h1~QT;kj| zb`ZHWX^^O6nB1SGb_ib|10v_AW9%zofAQ&?F+|Vx6(c@hlyigLtnJbTll@afXN*~Y z2NuEu9G6}Jscx)dY+Cnyw=LQcwSQjHTwT}c6T(LRwFcxyND1~ ziV?fmT_%CEo5FCx**QsLs>_*U#YaJ2bzsWdQ#Oye5%wCF8ix1O;<r)^7ke;@jWbGJT?ia~he`YOQ(e3Ol@_`*yHDVJ^* zf;CpLOoxi2(nyw3%X>SJcUJEz*qsL7`)PhW-E%-4I)=V)x_uDE&YrSX>%L)YIlREMlfCRHwQWl|_)b{-v5dwhU zY#l+F9sqz7{S6DyPjX*MdE2&oK|wT40j5Vl(nANfXqh{$;uq|6;3&A6Ly%PNIsxjc z+t2UE2`xO9kQ6WWMVa2PJ8=S22Q0Wiuo=J9U@b-jNZUZY8#E||{f5}25=a7PWhM&C z`U^*WWdGV1=)%+vTRb#<=x&Ys4p^)Z()A%Kn*GaEy~&dp!4YD(-HSTs`%6YYAL~@W zr^*~2*OR2@wt}v0UD)m;=B`_`#{sz@T$;hcyk&LVp%SQeEa-gWmFXIBNL>-xElKMz zB7rBVMAX>9l2WzeO?gQ(F_@3JJWwr3p}w8F=rB7k3vA|duI|7k#d#Z;FULJYGPx^{ z+4dT!yHGx>Fal@zmkWq15I#u*{?f@tK*XYmCfzmmsZRlNfH+}vFyB%?&T17!;g+~) z=r#=R2PBbYFBW{q&g=&-Vf{L&OAH@x`b(eoyZd9mE%Q`Dk~!+w9KjB6Ek6U*ilJXJ zzQb6<4pL7(5OviSb%J{t5y=I|xSKD*;NyRw=+;gR#H8K2e{?XEcE1eLEz zu3{8dQMAl#p^rq*p~0;pEEXhH5D9?g4-1ULXi3X7yd+4Mx_w~BNT3|KFSmD%r5U1| ze+qTD@#UD+v;gLjZ!V9wmvG1lsr<^K$o-tg?5DP5t2VMY zlTpaTl!7bQIslUoK>c4eKW=|7RXB2dh<*@&0+&?hG(T=dPq5#4w_17A%OB6(Z4Q!d z+nV{pZi>NT#JMVG@`jmjOs#VcuZ85lp?Q_m!0CfqT{xd=zpu|LF^MR+u{@m`8hO!+ z>-TAcKUS{--{t6eUVL5gJh6f4o2w{g+N(~r{AkVYWAiHoF}T+17{NEV_aV1~$eeVl z!6Y0#pP(%Y4^dvKSgbTKp+-TGejKWOFcQ?GKh-8g~=Ji-FRowM2_>8xx z7?a*lx=M-XgNKjIm-)L7Z-d(|v{Wp%xp{7u?!kl?%(T(9iDAgzg_Aaa-Y5q#}an`3FESK|992*iW zG)u+P)H?2~ZY+m{UZ+BS$B$c2S!*T89{pl=WiDdMe4TD zHt76jnhd<~Tv6-r5#WN^S(7H%H_&X-@Prl(u7Z39q1$EYJRa|nV$*k#g80L_ zhvT=9eb4I%t%*woB>_K9sDuBm18uav9nK7-r`suRt_q>if0R_BZr@?Yw5pS^EvBN>oxxwc{} ziG=E|zoz;O#rhZ0)7sw8*|61v0n%RcUs)t1Xyp#K(&!!0C+rNNj{d z$X;I8A97VDcKaZS+|r9Qree`6Ui**J6MlD9o!1T~TfMnYzdC6p99;*3^13mC>Pq$Q z&kvYBV~HsF$B-$})U7SRd!2qJS^`GKUAXEH zq!!j!@hAY4y_^{jPUHyQ^r!X#2-GCEp|hqA<3Eh0aW-Xlz9lNEktVVIf|dyx9-vBr zq#0YAKXP1(e?5c(>Wdh(A6M1SHL-wcCkzEJxYpOqwj=a#SQilhgbIlOzR|!5JmJO}I;aX`@eU?UeW0SVSmI^I z0xrw>ucV0t>O~H-#g0E`JrMicj)(}qOhSMWVsg3Nt{;hordnMNU8%i5Vqi$Yykuo_ z-ufy?ky9uK1YV&oT?N)F$Z+4=jp;YeKBjCb8NG&>#1Hqr6YGW5xJ2VfUz8QHxE-FC zMjGvwo1J#5*o(Z^8NmS!^4`iEsPCtZyn=i`6=@J|?QLCF8{k+qI4(H>+e5jRl$-4? z^*ij>pNKpC-ZkfX5SamfD6up-@11k$`&BS=7Y=<}Qq^XrJ9{vW4eHKj6IQMcy`>q9 z?y%NMv>6zT5%++j-0_vqfyZXtT@vcJNJT*Ad?tkbW)CDs*QjwA?=N{1)j}y{`(? z{z?`}r?0*|->mvR91W1SbLF#tWhu|7QuUo+w98Iu%pz_Mf}H*8&&q{BE0%+|(Oa;1 z(XnFqdPf_4LY_NIhWw`p&k{>23&6ALO-XIx#|FFVtw*KpebkVm@sHVu+Z46XwJgTP z(b3Nq8w$}bRl5#heSRm)H@^3YH^@)(xD!6-XM@DCTcv$3_j8@I>h99x$Wt~*J~rw$ ztNOl|tH@Iq@>MyrsO(%T)xK91R&!Nbq8V16N}|Mkf=-M&9y`O$PJF)kkM`7$7_oWH z;n(O=UXLs7UQU%08mb!y(boh5?^Bf-(a1n?&G~LLM7)APu>d-5dRm3h5zp0 zW;0`FV>U8mV>2@|;b3PsGBn~cWMSjt;bdj!G+|@@&-fo$*_b(h`XBxs@pt?G-{F5? z;res`1KZF1pMOvM&-fo+i^KhtKm3$G{FFcZJKFmxfB0uN_*4GyQ~vPB&W3-Y;^Ch# z|NA%jAAVB+h2yvRpR8Oz{a^o<_#6I*zxQbT^gsL$#NX@We~bTto#W5*znOpf-~T=F zH~kNH^<8=UtUkT#&ZO` zHu}Pizb9-#;qX!S_m3uFuy0ACN1-nztU zcovdf0;**Ys#ulsteM|~w(l->EJ06kRqnz(}Vm5 z%ea@DuebB`wzkpBNC+|Sayq-BL#a@eb#xvLDkHqa=@&2Xuaex3mw@&nW$0T4t!6PT zKz^GI$`iP8escy!(kUI-(k*9^n)C{dYNE4|t0|5y4If_q?JUl+4t4Rm26e~>~u{8S}$t6TSAF`uzpz6yv1)&)!oo~>mca<#Ppx=j^C2@8$ zRt2aeN9n@uhF`ws{2%RZewW8PT`iU4_qg1htL}WxP(Ub^&(6rtrC-?2g;DgW^3e^g1()mcwC+L| zLzf(F(UYc+y}@G~;hx(ZjI~>RHrdvoLb4932LxaXrxS{cnEcyd>*c`ylS!~P);~HZ#UpM_tDDqjDpkE25T2SLuOHI!+d8a zsVPNnRB9;zyGP$WKRslb(KC%`yQ4loUQD=%?LOx0_}4--({vO=rX59e>Al4p!w4i{ zS>&Vv2s5-eTyq#L zORB(MQq8AAPa#J^AU<|JkfkPJUW)UuI|Fgv=xPs0 zJ;z`W#lz|S%mU8S?TL@CFd_Ji*5vond3$c#J^^FZff&8I_rNTO+$A#SUQWAR*Negl zLb+Z-j~ue34xs<=6^Dc^Ua1Ccd!~#y_GIPg*}y!ycAT&)m46Ayu-ywl!xCA5Ofx2= z^<_`gP>SK0jF3)?k;X*ZJPM~S$4H}Xr!;oJ#RL|tBhP&VRBjarOy|7ZJfqJ2G#i9T zB<$V$i7DNMhkaHwoRlb zxdTCS&`T}OfgaC67PI6Z8U>cr9kd015#~>m^8b!9B4Jv%7f2`N7kXR^Qlz^8#0S4& z7Lhh%$SWI=ot8Bv57#aFTKOjJg(SlT=9F4kFH{a+A`eYSf55W||-^+5U5R4WtxfK zzc$ieIU*&Y&I!LQI3$ zk=Tm>pkI1R@5_uF(M;k8#{rp(A{3AoqyXS2L^uc)Z=o1QDu5@2c&{AX?4*o zN@f8XI$bu*l7VT!Pn4qK1iB9R4?Rznr$1KOds8R&<+Tvk#uOfZY<wpC2(#07{|s|7cdW zdtQ>B#q!=d9MK~;h9A`~?HA-yTird$@4gZBc#3+@GucYC?sGDD@f(G6pJ`BNsm7%_ zc$_2=X}e)n$A;>QyV7MejC!zcQ=Iq6Rh45n{a4x3J983Kiu;N))B`|3t#P)lcdVK1 zrOKwTM&k`$Oz{JCCYLrAgSj@-x!f=GUOS$6Y_#M_+W>x#JZ?|*C}#yWOIfpPgU7K_8|S+jys8<))3 zzCUg~hchX7h9_$30Y|%O9i5maWuQDsEOsK-qR7dCrQ8YHz@MzY_WouL>dVh4?^_mI ziG6+H+xT}%972nt)WP0_9p?RFzP7AIReblRTuDi>-*lQp+gg14X4OyVR7ObK4e-$= z>jQi3Hghz2pdB>69j!I+pZ<1!qgy~q%mg(rAea^=xu>5YIzfgC zTQ5|d1=)T%UMy;e2Aj2zl0WjMWxE1!CTq&EL}3|vARU&u3^?g(ChKbbGFOMz>J3P9 zRAizK*So|!-+(A<(v7wv55-3>eh0juDg@}nlrgj$%SJ`GYpyOMvIh-zv#x3;^N~E? zY2YfF)^kPz*g@=N>+jw$uDq-rxpGC39-sP{SHcsTn?i_`^eeQfiF=<@sRYDFaThnd zCIjN69eY2%JU_j3IMcrD>l6cn<9{WFDp_)lG{P_=Uz>qi)&~*sIr0$!65^b$^l}uU zoTDyG7wwC)Svpn!;A4-2IDwq~5JfkCBI~~JboOUFWjnCid*w`(fqErLmQiSo82apV zgt&Wx$s-G#aRy=Hmb4EL9-JTWHq#_y8_MLtR!@R1i@ba6y=X;h0wej?9 z2(F={xA#PdLFc_IZ#C;5h5{K_II$oHSeEU^)!tJja%ZfdQM_}~83|;GD;Qu+;_(U< zLq^`cpmMMU-(jLulq*NnFU&{l9s_ZgplcFnY-GpLnsA_NCtouTQ|lVbRfpBHi*<}$ zw=ptrpPglw^B`Vs2bq}KPVHEVW&n<;R1Gz_ATML(Hcr}4&lek zA!oY&fnZlkf7YSVuGsZjif!1-snE@@{^rC2_nqO;eU#aZK=a1=NGZ#efTMjroq!ym zaiNxHeGHNz4~&C>L4; z7c_98KyeUFAw~smdJ@DIfvFO1qW@+b11qJi1s>xTY~b4@<$?o|M z2f#WNTnu!KWBl0Phg&Si>?s>EsEHR@Jgu|eU$kHbsrE_jFkufjj}$m>aOx~nQ3Q#o zF-pSEAJ_st3FUhR8)woCbO6?P8pH+mdkyf-f^<@(zNt$SoA;P$Dj`ASW0QR{~Z^OoadAXh$)4n(2(EQt>Lcdg57LztG0Qz0g)I7x|DVXc`qP&1Y7Aiz$<_Qy)Uzt#S{x;L z0Gj4uM?u2}T7l?kgOZyHxFezHXq=#G2>T@~OQk1Gl#x1C+JqHqHuDubv=NVEE(yCK zp-NAQ4yu|h7rZ*r5A(npv+~EP!~13$0dCvbSD&QUo`Ki&Bqt{@4-@^|m*SyoiDvh? zn_7c4`!xj{+I$YDe1iT|6|^DL@hkC-@Gqj{T^g;x^alH$O zA<@}wm0yMNbqYIjjQ((h2J2_pYC$V~?n;Jkib<(c&MhO4`VJ_*z@af@74wuYjVP%F zvkpxf6wFz{a+gnwYV5h>P@qq+;700QDv%$Le<2<{?N0^DY~l@|zF~t$v0zZ-D6F8f z&nd(B*xs!WSY~Iib#^#vMrPsl@Rj_;*VSh?mt^Y#rvH^iUaO^S_fEovmo(PNn8IX< zRF|_6XS5TI1HAhtcCUO#jd(fgN0taeW60D`2&BI!e~9!krQ3QNnwYN zj%Ui^hfiP0p6IR&&S8;rl*?FDrbVAH41@3Vg1lmV>ofl=^Rgx29L$W=qW~_{hvn&FWUarR~Q}2rJ;J=((mg@l zG<~^1DQ0$`651CAmM9T4y&2W>eS%a;h-BZgjx2@o93Pe;li3shef)f}68ocG3t~Nn zbS#>b1!*?asBF43#JY8({Ny%7pJDXaNn%Cp4x|xt@>!ql%f0)~X2B_dI-N|(7I|)3 zQzs35EJkCFy{wT2S)+v~Y>=zt2wYb<_N!{A)q+|+`+lxl=U&qfl60Locm^pADrF<0 z0@hBAmUWyqc9e$ZV*X&hNONdfJzJUz7mdw~$}}n9;YMhAY4s?w5p0|)R4@fyRI13E zua23{X~KF2XBY!v>_txV`i_ctpVlP#`4^J@0sbDH$E1~+uwbc9c#J}E{H_E0T>6NE zIc~TaQ>jJ#s=2f-QYp>-IkPs%ybKDsqHqLKPI6A%fbxl@*pM(gk=K7_cqObe3lNIh zDtve9prZI$w62z7U{mRbN`@hhG#m)V#*8!z@vQTC9XfBfJ-66Ap{G3|2AXbOL`5vl zA|)z9{=z`kK3Vcv#z?3&x9%j%Z*&}W70rv2*qyxxT>enEOzATzni~WAM)6Qi{)J?U z1}0tOSa&R@(=@+~#Zkj0;OY+eEZ4SF=rMstw^qOGwuh-a*_1tb(#DLqy(Pj;;b14v z$6Q>H)}LvBO*i^()=bRe18ONm&g(TFpBD1tkvc#P{=ij;Pyw`-Cr^MyPUy{*Qp^bZ z3glo{(;!PWdsUV|a*pmsG|{`hY)jNtDL+?!!q#8<1pLHuje!T(P`~_ovu}w&N;*bj z$^5c2P?`}kpQGe5Oj3Brzv(};Jm$B4Uz_mr<$x=9-#{V>EmdrRRVE`WCeZdxm$s^y*?0^dX#91*MW!T9SwDgt-2qqI)cNS3 zxq6?Npv`rAR7TB#%$&m?hXX8ep5=JdlcggQ=xBLMmwtx9nBLSVOmG4Pm^Vk{nnIzNL_tWleM+J*9?Dzv1~ zj_&^0vKEFYcKi&9B=yl`Si7a%TZ=y3G=WZJM%NQEZD%64)!XBEOW_L~B3O3?a>j;I zd539-!YUJv_#1m$ld>u;BL}U?C_?HBoA=(tjV%lejPVBD>*d`!d^e-H%EMhn%il#q zB+p#Rmscf~mpg=VzUz3bmDg)Nzh9^`UsM`u)dIcQrpiNHyrF7Uc&Nc;3mQw!tM!~w zOE`MmiWrggySjkfC*s$6ruGoj@9_4}WV5MU0w}LEIgnG$c7G*+Ld+`IR_!C5sXtGt zO7f6ZhmK)+%9Ov#^TJ)T1GHU?`^ALgKk_1yERSV!;Y&F*%i2uJqqUEt4}BPXuXWDM zVvpEH^SFU|i~D>%a&=(!>}E4(4N%A*BKqV|!)o&RI5>$?Jtu)F-LYS4V5an0K+xmq z{_46@Y4uC98R$zHk@c3OrQa|Q^+j6Aut-S=Co}S@Pshu%*UoshF5k>OqJfm>X*Ucp zz}aAe{%Al6I&mGbScg_5SvDixtm!d;+HT2R=;^v=lt0C08t_>)SB!CzV69RX!n}g6 zLai!c=9K4$+Vl?JDmNZL(kTF6QIqa|M0YdqRG*jWwt5cf=(?r4wf7 zT)eJjUc7~)LEN>s^2({(=sswfXCji23%Fm+Xb|(egYmuN^H`^R=p1@8@C6? zHI9pb3=_-4Ynt`R+Us8;j(a}dkLuj=yE5vwn9R9$SK)OrUU5U5ioUnzU4fI(-`yVw z-Op`vJ;K8lg!wr9%ddXB1FMeKkm!9mCE-cdVCr>Ke#!6JS0v#KDQ<-KInmngeyJhi=C^U* zCRhNK@-O@1t#MAm9>aQe2{@QAAt&V&u*KYBBMk5N9@YU@;&Not-uK2F!|sp&sy=JI z_y#FP45S04Ch??*&uI-UwnJLDP(eU%FPaPD6uOX}pdn%!+-J2TcyV6_Mn~^&TfBLe z($#*r@b~+AapLn`0l#Jki{IuqJI~D!UV6_jKsrr*tp5u--GrITl$qU#ozsY$ z+t8GQ-IT|SorT+&o0FZxjD>^yKgEBua{T1K|1I%%`~Tm;e{-_^1^>{`YUNW-w}U<|Nc9V#!vqHeMNj%F4ye`RC)Go#SWz*S{tH0si|B z_5XA&Q!xkQkD#RdMZbZ`wb3Bzqe8}`D81?@2>jA>^Ewahh_n-gM$fWpVMxLBlZYSW zBrh{tMMX_#DprV>M|p~*HMz*i$;s?Y%x1jUFCR;DeR*%{2?#*&Fgy1j%~?;`fiP`I zIaF(w5J+hOAXmR#|EH6_C;GW6VV}T2Eo}UdI(XiS&gUz61mdFO#7uvux$2;$;#RlV z<@Q#=^iR~CC1S# z4vLVWbf}k)Ft%sXMkgjzLRx_d@6ohbxo(z@zE3Q5Aj(K|KI4@fcRZd?492X#uOwvf zB||$Mh{hj0Sq5D-j7DNO3JAr{$io8QYA9wxM8O~CU=bt&bUY!X6D|U*Sr8X#kW*89 zd0Y?I>8maJgwxZ;pnH; zss(jfpW9Y__sln3I*;)wB~5hL-JiJ{N|qRmtdV2ei?Z5h|s>644fSl;m zfL%J)d5FZ&&n$4P&8V=fQNWcs#JH+vTY7iR#yPb8UTm-WOd6KPtdx~v zzS_~!xCyy{HwMi5RwjQW7|V+Z@yW#C4GbNzWY+|j-8d5y@nR}F|25>=XtzUbzo|UW zRH&?S7KBk^)zvYTOXik7Y(+23lNh@KLA)f>aKIe5Rtn6QZ3FZ!@|yrp;>yY=$BXU! zx}6-Jhxh0whwH|&UBgcgc2!jyqrgftTs9M_fkH|tyP?QH^`-Mm{>|+uJ@A*!(*Do`SLPLD~%Zp;)gDG4X(CQ#*^eAW<#whQZT&HH`-wN zGZ~37;dez&28HVPlsdVeUT)_)85x%U)Sswu`aA>X@Yok?`oT!(|G0T*DJNN8w3i|DZ36g(S<~Mmcafy zDs5>`))97`eIAFP)xZNPJ|Yb1O2Xzh`~6}W)JF`qn>le^^Ki5AafYp2m|CU=9m8@| zT!Oi#NM=ruy6?0fQcpS}lTR$!Wii;__!h>0im<)tU&O#bkuw`)2TH`ZTK~Get89yc z+oChfz>w12HH1UFbhmUV2q;|xNH-!evV|^MwK?#-^UpmyA+f?F~h+ zsr+@yM#=1CY zrA~`!KsuK>?#l$69XT)SuAPOU9&N?p@UMnxm&`TLj6pF7j-tKOME>XYg(~&Mt>U_A zh^XtaJLBO#HQ?%Ws<#VF+{xO_*76m+_&d|D?||q>{})Ce?JWOBR86^qlfp{3#!~^2 zjF%cUud!ll_zr{}q{-}GG#2xJ4mh9R{q4GPxH$L9@iz{dws@W|Dq9Rhf5K9IUTH^> z8BL*tML|jFCT#n9^2QZK{U?VyH&OAVWgQX8AQ&v)E7sHx%K^t%-XkIG^2 zN!W49Q-79!F5>)!tF!Re+i~`vHI#7t`9mkSCRAH6>6Hu;iJ?h$IQR(huBQ9&XC>AD zqLS;++H&$yBtL=qpY0mSmeEdLQpB&>>#X+8k{kQ)TY2V{25(DyMnCpO?o;G9_>+K_ z)sqF$r@+}F#Ce9`Pt2|m%6J$I>J9w=Tcp+ zf&UvGMmyI@$6(uZ-Q&%ebKzQhHZ9DNK~@>+J{ z=579*bMNJO^nGCgk3-vwW>HRZ1;{uxzB)pXn=r6%^}Wq&JO&Q4rWY_fY&vK3oB>-Mydq0*Nc`x-o>4N5fY{98fuUke<`GtaRI(=K{wMsMtxeu%8iXzQ> z9iHUJm38^rFxL07&+>At-+#8QHno?a5Vj-PAe(wa&L6!3{F)1opu@Dj$9R$B=MKKP zXO5P50~PbxO3W#!<|n~V#$?c=VW_jJ>4a*YwG$76Zcwk3#adbwsG=?TeGLp@?^h6IFV7wNwBnWH%5X<{nMO;GLq0KbOWYt;OCA%Q zpsPJmJC|=%_Xw!n@7ks$a@O(6VDmelQNd@cW z#p37HauB}fT_LPeb>j@5h_wR@#>XbD?o^v&<^N5XjJh0M5X#MSVq;JBlr6m$F^x)V zmX}pQOm?_PLKY=L-LBVEAkMPlyg%}!>|A&U^o@J62ID7J9o!61O_jOE5yX}=TR{Ob78io_d~+$j8~A8o|z zxNi?3nSTztGXPM;;L^ehbj}}=+2KRSHq92$@`(ISBW6IP*K>U zV$^H3a(}{1&ukl!rDDeuhKv_dd9z@0EkHZ6@!cS&*3;Udh;luow(>0gWJ-RM#^d$s z%rfT+3()96L+sCaG?;XoZ-gDQE%iqxbM8e7$r>Yb|Lv!vnE{ zFbgRi+Sd(PV6!uF90cD5p_>}+Cny^z*H1%F&ArSuf@W|1yRHQ$6Xu!Dx{b$q-@%bq zN9hU-*uBkIZ~MAnAV#KLA@$oLu~&DyC)$$e;ERyH=NP#b;c3r=%uz`=E3X)_N~F=4 zni^`a&`sBg{U;5{j5@ER(3_gnML+c=W9c{CPws`rZT<|Ur7a1+c1;EF1xJ?WmJpQT zRJkLzC@)f4&tG$el*IqMaj=ybvWyt6+pKCWo2J()6FJm1i{=t1c;=7NEfgj>(+?vr zN%6S!|C&+K?ZO*oT;^ySK;x7SxM%M@jyEZ4O%;TY5D=@^< z1yc4j@R4^!R^hQnxI7coirD`9{c1&!;}*I;)vDR#RI$WXC&14fM42E{i=oe2`SUL? zUE7=SZ|vv!fhG^tj{Q`J&=45Gfo`(#=0veStqT(O?)Hjc>#R%E<;$orGF2tYTlcAs zsex0%jy@T4B4D$=XB5l#%%He48#c}Ta9Ga!{mC=jW<^$75^LNHDJ+qDrmN5g8B7_{ z^+t3CeyH3(7Ueq16f{w;IaAc~qeM10Pr>*0IO@VTTE!!`x>l6DDJpPt;VTwfhO&hM zBZUx_*kpp??~I9-8k`<&xJ+jw`{!vAg#z%>Dqth z9WmG6x4=m`Ro?FCvlPD^;NvCNT{4X~3Q7cD^4SAg>g$zH^+(n%XIkXpM-h@Bt?3P& z4SB!gEUU^a*R_cd>JgFAdLt|Ec|74uS-sIv>Hy#EFKd-FtH7e@X0wON$>la{YIZ)~0;2Xy|MkRM~u_XJXIyhavnI2SP-D$;~{ z);o1t@`mP9sDEiDN#ruVrC_8z#Ny!EN|#%FirZBB@3BGGA@XnWEKljYnoQ?HIUTpgYah8^&Bc&++JsTP(k_% z5g)0WG`O%6ek*suP1*VA%LA)ajjxRvTD4LV!PkuaDEI{K9)N|n++9U2dTPT?^jTp} zE&lhxldk@=X}N*Zty>V-FI zMO<$tHONSeQDHhSr22;#7jWr*2qdvR2d|M$%{{8g$h_N9Bpo4BjLt>Hi3Zmo< zTFL%6@*>)vvEERz43d`Kwaub~)ZUdeB)jS6=W5XJGC{tY@lT210$b_|R%~mzs_!m4$`M*z zfD^e{A=PH2CcwV)_sHik6oYxNHcv3VWly$pb+qK3&oXFZcFJ0bC~ay{RVQ1*>pA76 zfHWpI)@Y#uH(E@i3E^DL8Y9*P8zkj+@v#dFt&e2`oaed-#BAmjW}Dp2B~m~2gro;v zz%eRe@_)&8!BRD3oS&_Y(z1X7kVzeDkGnPuajjfV0jBdO zz?!7A9H5LPI?sMbxzo+6Fxj%I6Nx2gKQdLrqBjUT?)1PzjUAK>uBVFDMVLC98>o(~ zbn&Dob(ad`xxqfDyrf}b{t3&p(iDY0b=U6()3tj-igNcTX-SptB!cA_)pTY;efNiB zObqm7RW-zCI0qt4f=5_($cOAPgg!GjX-B@vd8Usx;Ezw?7Lr%$(A8_LEb}ML_s#Tw zY9yOdEZhw}{GByK{Fd_7765a%GpyzJ6WL&|8t|H=7(QHn$-fEwRZG_aG5Go+kx*Mn z@Pd$k5?m^}EHB}K>SF$GwNouBI{tw=v4zDKQ2BR9xGBf-j;RS)IwjWr=^BA2pit(L z*1>@0YfjMk1WekFp+AO@@xJyM234N@7was5f~;$AbyGeK-4v#1651K|R+3^ilIlH) zw^D!Rc$lNOfMgta4mAvBONj;YZZoAfP`Pl6ljAId$xv|m0hTyB9sQb@!cw75CB_dyO$9v10%wM;la=i@sf_RPSB#Psu6M527gW;1 zsvRLegxQZxl9^;2leNM`2MR3&E=C8DS1yb|fp z*SPMA-A}@FHJ^3-eO|}8y>_MXsDREWp73s(LV>~9EdxMHl43XI)QI1d@RG8=wLmRL z#&VBPvU=EVBr;9Ua_QSYoL=A|WQ6l%`mI&w3QdLLae!uF&-0WB{xYty*ECz7MnSp{ zYruygT)V|jmKdMh=VGJy@M$;X7MT>^qY)dqQt4Eo* zm&tp25BcZ(#!O;bZ_D(&XdCg&)EOGxXWeljA2Z_?iw0A|;p52ODt67KU}gub;G1{3i!4NvPZUZZj^F z+7aIOzFk0`kcMQ~yug=2vn`y8-PF;72*|7bqtRBK#5WhJI@&dE?2(B1OwiV1o61q7 zknnBE+fOW=M3Gy(AdN$ex+yUO38d}|`8U`2=(Anp1gHzS<6+W&6Ap4R|qhaLOm78D5v(IG@WBX^uMqVdXa^q0J zvhwLW=}M%RgI?*_+cY|)7PB3v4ddf!*X`;Xh=o+(-q+A~u__txzBgm)O&bh42r4^G z9^F{}H{v@8yk3M}!EGh9oP;JpvZpDfpcpnIHiW9;C(09#Wmd%Ps7&Z~7){$4*VlIl zD$xF6Q629%On-7R_s@NdJ&IgJj7qNVAE$as?}8n+L|e73VD4Fg2wv_}=#i6u7g<@9 zcWB;5YfM@ynjYe#$aRbr8FhN@pS}IZay^ zD-#F%MYTgj1#i=+$mqnrTvIBavB>PpC|z9UaIMdU^gp!E6{c_ z@dNk5d8UdQQ+*mvQt-3YB?8jnKNp=u-f3ffgud|0tNAkhY#mUtua=j_uZoj zq|J&0)8V|KqK~>s2#r3$3Pwg}X2*6fB{HNCKH@({#>c z`jPCi@6{|Acu_gc08qQ-M9@mrO7yYfpueRWwFXM23+M9OCDj%*IFvk}%!ZW`;pb7D zqupPUPV(T=gob{tH!Mh?FHKQc1Rqzko3t_OSn9$Y8OW_^qXVDEb%6Pwl{fAx2#>v> z7Y{}c8eycS?wsmad%*0v-(_DXjKOhoQP5x4=Dkgw31oY5(7wVF%)Ea}RAiBRABP*+ z=6f=MrOC0NWF@k&AOd&(xdG>b6uk+fV>gNF>PgT9&KY5Z2fQ@{IGq=RpQl-rgHDKJ zLwKTsJnPdhtYr6#Y|BdV(&MUU=e)fN2DE1KVt=>7qKo{W6CiE4TW|$|NrY^vgl~SL zT(PJGXuqJE^H_(XjV{!BD16mbM8% zB$Iq@#!&deCv^qXM5N==w3-kZv(PUdUds%U8fubVIUh~VNd2S_Ru{<|%H@u5N-o~3 zkTM5Unsz(JTLV4suYoK2LIgzDhwHmVUR-lbK({b+R9n2zY>OG1ivKB~b|{9i^N z*jDcI;g4G<k>=ZFHo{so=*0gEQ^;Z22_u5|kI_R^&gxr?Z1Hzj58K1n z2LrD1YoRko7XGBF$C^xJHw0H|d7wmeCej5v)XF!S6^~hzbzI*N2M6;k zRJjUIO}RtE-}Z=VaZW8j5Id3V13!e`d$p}`ol_BZKeF3A_mkq8b^yv5bvjq0fr@RIJe%bz zHTY@}B`dX&EK=*kAgf%4bQXHnAMu!CkA}-Ja&Fh z9RIOaat^EwFag@U52M4!_*Nu8ZRIU0&N*TEMMF6`j2CFtfeh0;j0n_TedbH_tYT*; zo_=*hwALjNv74I4xaa*|h>L(JEiMylo%mosqjw9}=Oy-N!)&PpSK1hE$+*>##(Wlq z0trrn%pf_c6>$h&E{%t0;8>{N?zZb>^UTU%aBy(Tx1F4l4y!cW9xWTQ0gB=?WrOcx z`1l!{)vj)&h$;m8V+D!!whhKmX10?0iAE!A^-5n~0VSND%#EQ9u$Nfd#<|`SJ8uv4 z7-6*H1T4Ac1S(|^A_l@I`ENyQgoi5@B!Jh`ws^zC*Q2TLvwiBWfw<-rP*5+OI%*@O;OgiLZ|tQV~F9=vwsyyjR+iRTrONZF*3O z2fLy?iI0@&_-H=W=8dto7ID7o8t@>F;GlTu(`M!kCdY7%B-%@r1o`Mj*1gi<(@*Hp zmRzzK?joKLuH53;tQ0|l_T--Qy>;HqQW1QQt3fP#3R=NL+?;dpq4{;`}UgC*O9(N^;Y z5o#^h<>s=%btgkNo{KcCgK6%Qn~7n@=0SrxHZ04*f!%|0;4u{esW-?!A$7 zuFgm63&`LB8klug$$rzTOE_coI1@kHhZ10YL0PhZZ*D}zo|dNCI(Tgysko*HKb!;3 z8eM8fr*+WVzBgVCJl=guG%xFZu=UxQFIa3&9s~7}&gAAiC+9TGELSjehm7Utj*M@& zax$lOE|Jj}kT|k6$;9F?>DbMD#MJ&KB>wvJYCneZ(RxW=w$sqAvBVq`Qvs z+igq@vw(Bn6`;lA)(}_gy_2g~G3J$z$;$Ow*xmK+S7>tc{YI>}KILp$s*xHtY@~UH zsd?Z%=lN6AUjcdiVSNJD1ZoY&QJ*^OINe$PY5t zpTQV({rj&8T>Mb}2r&mXyIdeRvVJQB`{wRNY=4SX&oe$x)z-`5>Mn-+pSSl5_FsGsTm8lb^6-AFh1pS7`-?Kh!slNY zyYF^GOZpvuY}_#h*9M$z@oG*dNV5YBo-m46d!22Geh>LtFU}tb8bs|2A3I$hEa4LQ zn7D=~dp+$>Uix7SqKj@Wl>($KkeX#pyh}dvs!i3lbtR%&=!=DK-BFe!B~mh1 z#A_oY0ArPS2I;11kDuSO7%zWj4}QaOPO7558|bXvDZ(k~HT#VCGoPCJXYy%r@Lh|q zUELM^E{qfqGtfEjx!d02b5#GZFGxf-Ob0kLm{hqzeK#ZA$0|Y+`tDA*c`xOQdmj9g z#c)@wv$@r&(eFemwFwq${2O!_689_kIwW4{D9l1H-N8*J>D7hLmWV>!3j_P&iAmMg z9Qd&6D~+qSdmY~EU9uf_F5aU=hi7H)d074Kjv91wWd1aC{5e{vbL_aDWc@A~5KG+W z7W6XBd}aPMk0pkU+KZ`HrRI=rhO_iHkZ516`%?<|sg7%#@S=4w!CloMKoad9UJeL2 zy7jKyv5kv3+#Vq>k1_6Pd;N>~_ssJnW}6Oz`zp(z;iC9Pzx5`!b!WO;6?oubZPkz9 z+x=qayMNYYA5P=vaN`HB@keK>!Xh5WI|2n1hiO0io2ZPTR~a8pqJGYPyHld^l4c$i z^P!+P`Es6t=b9zu{U#XWuOJ<>!0={Smv__oRSJp#Ia#f5qTinOQo6(2&+k^(1xMR4 zYjh-0J7H0RC&@Lm_eVQ1fA3J*Zq*>L3Rt;{Q~4_;g`82{kAKbN4QN3W|KQZ%Y6i2l zyqpgEGJC(14K;z#w9sLK%#q%mf4X`VZwDK4UGaBZJ!?zM57U(E-SWiIDqjER2#4;1 z@?_t}c6(|lGT6FIVL|jni46shAy}6NMo(7u+aSE?jw^t@(nu{dgoz zzQuPI>w^ox3{9hVn~#v Date: Thu, 28 Mar 2019 11:28:14 -0700 Subject: [PATCH 59/78] update package --- .../avatarExporter.unitypackage | Bin 75752 -> 75756 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 8e7aa0e7aa6032274a682af74d203194f4337538..2ce40a8a8f1f47dbbe8af31472a91ed7ef4a2c22 100644 GIT binary patch delta 61406 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

F9_?)@(;ZE8 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 From 8627a92f299778de6aa389595ef59e05ae427f0f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Mar 2019 11:29:34 -0700 Subject: [PATCH 60/78] adding functions to update variables --- interface/resources/qml/hifi/audio/MicBar.qml | 6 ++++++ interface/resources/qml/hifi/audio/MicBarApplication.qml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9f970faaa9..89a30b4b91 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -34,6 +34,12 @@ Rectangle { muted = AudioScriptingInterface.muted; pushToTalk = AudioScriptingInterface.pushToTalk; }); + AudioScriptingInterface.mutedChanged.connect(function() { + muted = AudioScriptingInterface.muted; + }); + AudioScriptingInterface.pushToTalkChanged.connect(function() { + pushToTalk = AudioScriptingInterface.pushToTalk; + }); } property bool standalone: false; diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 509517063d..a39707e052 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -31,6 +31,12 @@ Rectangle { muted = AudioScriptingInterface.muted; pushToTalk = AudioScriptingInterface.pushToTalk; }); + AudioScriptingInterface.mutedChanged.connect(function() { + muted = AudioScriptingInterface.muted; + }); + AudioScriptingInterface.pushToTalkChanged.connect(function() { + pushToTalk = AudioScriptingInterface.pushToTalk; + }); } readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; From 04d9858f028fd642891fdb2ab43036df35cafb68 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 28 Mar 2019 11:43:53 -0700 Subject: [PATCH 61/78] fixing remaining issues --- interface/src/avatar/AvatarManager.cpp | 12 ++++-------- interface/src/avatar/OtherAvatar.cpp | 1 - libraries/avatars/src/AvatarData.cpp | 1 - libraries/render/src/render/Scene.cpp | 2 +- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 33cd48a047..aa1847f64b 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -542,15 +542,11 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar auto scene = qApp->getMain3DScene(); avatar->fadeOut(scene, removalReason); - std::weak_ptr avatarDataWeakPtr = removedAvatar; - transaction.transitionFinishedOperator(avatar->getRenderItemID(), [avatarDataWeakPtr]() { - auto avatarDataPtr = avatarDataWeakPtr.lock(); - - if (avatarDataPtr) { - auto avatar = std::static_pointer_cast(avatarDataPtr); - avatar->setIsFading(false); - } + transaction.transitionFinishedOperator(avatar->getRenderItemID(), [avatar]() { + avatar->setIsFading(false); }); + + scene->enqueueTransaction(transaction); } _avatarsToFadeOut.push_back(removedAvatar); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 22ddea14c6..11eb6542c4 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -50,7 +50,6 @@ OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { } OtherAvatar::~OtherAvatar() { - qDebug() << "-------->"; removeOrb(); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ee701020b5..26407c3564 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -132,7 +132,6 @@ AvatarData::AvatarData() : } AvatarData::~AvatarData() { - qDebug() << "AvatarData::~AvatarData()"; delete _headData; } diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index d3bcfb1f95..0cbb7e1214 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -408,7 +408,7 @@ void Scene::transitionItems(const Transaction::TransitionAdds& transactions) { // Only remove if: // transitioning to something other than none or we're transitioning to none from ELEMENT_LEAVE_DOMAIN or USER_LEAVE_DOMAIN const auto& oldTransitionType = transitionStage->getTransition(transitionId).eventType; - if (transitionType != Transition::NONE || !(oldTransitionType == Transition::ELEMENT_LEAVE_DOMAIN || oldTransitionType == Transition::USER_LEAVE_DOMAIN)) { + if (transitionType == Transition::NONE && oldTransitionType != Transition::NONE) { resetItemTransition(itemId); } } From f2b0e47c39aa56d7277dc237a60a6b497939b50e Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Mar 2019 16:19:35 -0700 Subject: [PATCH 62/78] changing opacity --- interface/resources/qml/BubbleIcon.qml | 6 +++--- .../resources/qml/hifi/audio/MicBarApplication.qml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/BubbleIcon.qml b/interface/resources/qml/BubbleIcon.qml index 430eb19860..f4e99f136c 100644 --- a/interface/resources/qml/BubbleIcon.qml +++ b/interface/resources/qml/BubbleIcon.qml @@ -24,9 +24,9 @@ Rectangle { function updateOpacity() { if (ignoreRadiusEnabled) { - bubbleRect.opacity = 0.7; + bubbleRect.opacity = 1.0; } else { - bubbleRect.opacity = 0.3; + bubbleRect.opacity = 0.7; } } @@ -74,7 +74,7 @@ Rectangle { } drag.target: dragTarget; onContainsMouseChanged: { - var rectOpacity = ignoreRadiusEnabled ? (containsMouse ? 0.9 : 0.7) : (containsMouse ? 0.5 : 0.3); + var rectOpacity = (ignoreRadiusEnabled && containsMouse) ? 1.0 : (containsMouse ? 1.0 : 0.7); if (containsMouse) { Tablet.playSound(TabletEnums.ButtonHover); } diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index a39707e052..6bb418688e 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -51,14 +51,14 @@ Rectangle { height: 44; radius: 5; - opacity: 0.7 + opacity: 0.7; onLevelChanged: { - var rectOpacity = muted && (level >= userSpeakingLevel) ? 0.9 : 0.3; + var rectOpacity = muted && (level >= userSpeakingLevel) ? 1.0 : 0.7; if (pushToTalk && !pushingToTalk) { - rectOpacity = (level >= userSpeakingLevel) ? 0.9 : 0.7; - } else if (mouseArea.containsMouse && rectOpacity != 0.9) { - rectOpacity = 0.5; + rectOpacity = (level >= userSpeakingLevel) ? 1.0 : 0.7; + } else if (mouseArea.containsMouse && rectOpacity != 1.0) { + rectOpacity = 1.0; } micBar.opacity = rectOpacity; } From 3241d2f9602c9ef0d86643323b99cc311dbd897e Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Mar 2019 16:24:37 -0700 Subject: [PATCH 63/78] don't allow clicking on push to talk in MicBarApplication --- interface/resources/qml/hifi/audio/MicBarApplication.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 6bb418688e..c19cc54f4a 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -96,6 +96,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (pushToTalk) { + return; + } AudioScriptingInterface.muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); muted = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding From b9d04c6ebb48181166da49fa8787faa5b75cd5fc Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 28 Mar 2019 16:42:29 -0700 Subject: [PATCH 64/78] adding entity to a faded list --- interface/src/avatar/MyAvatar.cpp | 2 -- interface/src/avatar/OtherAvatar.cpp | 1 - .../src/avatars-renderer/Avatar.cpp | 12 ------- .../src/avatars-renderer/Avatar.h | 1 - .../src/EntityTreeRenderer.cpp | 11 ++++++ .../src/EntityTreeRenderer.h | 4 +++ .../src/RenderableEntityItem.h | 3 ++ libraries/render/src/render/Scene.cpp | 36 ++++++++++--------- libraries/render/src/render/Scene.h | 3 +- 9 files changed, 38 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 298e661f24..c5175aff73 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -939,8 +939,6 @@ void MyAvatar::simulate(float deltaTime, bool inView) { } handleChangedAvatarEntityData(); - - updateFadingStatus(); } // As far as I know no HMD system supports a play area of a kilometer in radius. diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 11eb6542c4..81d88302fe 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -356,7 +356,6 @@ void OtherAvatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "grabs"); applyGrabChanges(); } - updateFadingStatus(); } void OtherAvatar::handleChangedAvatarEntityData() { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 992ee5db96..96a545fa97 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -682,18 +682,6 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ _isFading = true; } -void Avatar::updateFadingStatus() { - if (_isFading) { - render::Transaction transaction; - transaction.queryTransitionOnItem(_renderItemID, [this](render::ItemID id, const render::Transition* transition) { - if (!transition || transition->isFinished) { - _isFading = false; - } - }); - AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); - } -} - void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { transaction.removeItem(_renderItemID); render::Item::clearID(_renderItemID); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 1eb760b857..da04c4adf7 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -464,7 +464,6 @@ public: void fadeOut(render::ScenePointer scene, KillAvatarReason reason); bool isFading() const { return _isFading; } void setIsFading(bool isFading) { _isFading = isFading; } - void updateFadingStatus(); // JSDoc is in AvatarData.h. Q_INVOKABLE virtual float getEyeHeight() const override; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c235460404..3eed625916 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1057,6 +1057,17 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool } } +void EntityTreeRenderable::fadeOutRenderable(const EntityRendererPointer& renderable) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + + transaction.transitionFinishedOperator(renderable->getRenderItemID(), [renderable]() { + renderable->setIsFading(false); + }); + + scene->enqueueTransaction(transaction); +} + void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision) { assert((bool)entity); auto renderable = renderableForEntity(entity); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index a511d73210..4d6c0e3ba2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -93,6 +93,9 @@ public: /// reloads the entity scripts, calling unload and preload void reloadEntityScripts(); + void fadeOutRenderable(const EntityRenderablePointer& renderable); + void removeFadedRenderables(); + // event handles which may generate entity related events QUuid mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); @@ -255,6 +258,7 @@ private: std::unordered_map _renderablesToUpdate; std::unordered_map _entitiesInScene; std::unordered_map _entitiesToAdd; + std::vector _entityRendersToFadeOut; // For Scene.shouldRenderEntities QList _entityIDsLastInScene; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 39f9ad091e..b37e46d02e 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -44,6 +44,9 @@ public: const EntityItemPointer& getEntity() const { return _entity; } const ItemID& getRenderItemID() const { return _renderItemID; } + bool getIsFading() { return _isFading; } + void setIsFading(bool isFading) { _isFading = isFading; } + const SharedSoundPointer& getCollisionSound() { return _collisionSound; } void setCollisionSound(const SharedSoundPointer& sound) { _collisionSound = sound; } diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 0cbb7e1214..ad6523ce11 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -271,10 +271,6 @@ void Scene::processTransactionFrame(const Transaction& transaction) { // Update the numItemsAtomic counter AFTER the reset changes went through _numAllocatedItems.exchange(maxID); - // reset transition finished operator - - resetTransitionFinishedOperator(transaction._transitionFinishedOperators); - // updates updateItems(transaction._updatedItems); @@ -285,6 +281,7 @@ void Scene::processTransactionFrame(const Transaction& transaction) { transitionItems(transaction._addedTransitions); reApplyTransitions(transaction._reAppliedTransitions); queryTransitionItems(transaction._queriedTransitions); + resetTransitionFinishedOperator(transaction._transitionFinishedOperators); // Update the numItemsAtomic counter AFTER the pending changes went through _numAllocatedItems.exchange(maxID); @@ -408,7 +405,7 @@ void Scene::transitionItems(const Transaction::TransitionAdds& transactions) { // Only remove if: // transitioning to something other than none or we're transitioning to none from ELEMENT_LEAVE_DOMAIN or USER_LEAVE_DOMAIN const auto& oldTransitionType = transitionStage->getTransition(transitionId).eventType; - if (transitionType == Transition::NONE && oldTransitionType != Transition::NONE) { + if (transitionType != oldTransitionType) { resetItemTransition(itemId); } } @@ -454,14 +451,19 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti } } -void Scene::resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& transactions) { - for (auto& finishedOperator : transactions) { +void Scene::resetTransitionFinishedOperator(const Transaction::TransitionFinishedOperators& operators) { + for (auto& finishedOperator : operators) { auto itemId = std::get<0>(finishedOperator); const auto& item = _items[itemId]; auto func = std::get<1>(finishedOperator); if (item.exist() && func != nullptr) { - _transitionFinishedOperatorMap[itemId] = func; + TransitionStage::Index transitionId = item.getTransitionId(); + if (!TransitionStage::isIndexInvalid(transitionId)) { + _transitionFinishedOperatorMap[transitionId].emplace_back(func); + } else { + fucn(); + } } } } @@ -552,20 +554,20 @@ void Scene::setItemTransition(ItemID itemId, Index transitionId) { void Scene::resetItemTransition(ItemID itemId) { auto& item = _items[itemId]; - if (!render::TransitionStage::isIndexInvalid(item.getTransitionId())) { + TransitionStage::Index transitionId = item.getTransitionId(); + if (!render::TransitionStage::isIndexInvalid(transitionId)) { auto transitionStage = getStage(TransitionStage::getName()); - auto transitionItemId = transitionStage->getTransition(item.getTransitionId()).itemId; - if (transitionItemId == itemId) { - auto transitionFinishedOperator = _transitionFinishedOperatorMap[transitionItemId]; + auto finishedOperators = _transitionFinishedOperatorMap[transitionId]; - if (transitionFinishedOperator) { - transitionFinishedOperator(); - _transitionFinishedOperatorMap[transitionItemId] = nullptr; + for (auto finishedOperator : finishedOperators) { + if (finishedOperator) { + finishedOperator(); } - transitionStage->removeTransition(item.getTransitionId()); - setItemTransition(itemId, render::TransitionStage::INVALID_INDEX); } + _transitionFinishedOperatorMap.erase(transitionId); + transitionStage->removeTransition(transitionId); + setItemTransition(itemId, render::TransitionStage::INVALID_INDEX); } } diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index c8eafcb696..e2195cf8c1 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -230,8 +230,7 @@ protected: mutable std::mutex _selectionsMutex; // mutable so it can be used in the thread safe getSelection const method SelectionMap _selections; - mutable std::mutex _transitionFinishedOperatorMapMutex; - std::unordered_map _transitionFinishedOperatorMap; + std::unordered_map> _transitionFinishedOperatorMap; void resetSelections(const Transaction::SelectionResets& transactions); // More actions coming to selections soon: From 248f9ba375e07faac10a09dfd9cdbdd675a2b393 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 29 Mar 2019 10:32:13 -0700 Subject: [PATCH 65/78] adding mute overlay back into HMD and have warn when muted disable in desktop --- .../qml/hifi/audio/MicBarApplication.qml | 7 ++++--- scripts/defaultScripts.js | 3 ++- scripts/system/audioMuteOverlay.js | 16 +--------------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index c19cc54f4a..4e0adfd95c 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -18,6 +18,7 @@ import TabletScriptingInterface 1.0 Rectangle { id: micBar; readonly property var level: AudioScriptingInterface.inputLevel; + readonly property var warnWhenMuted: AudioScriptingInterface.warnWhenMuted; readonly property var clipping: AudioScriptingInterface.clipping; property var muted: AudioScriptingInterface.muted; property var pushToTalk: AudioScriptingInterface.pushToTalk; @@ -54,7 +55,7 @@ Rectangle { opacity: 0.7; onLevelChanged: { - var rectOpacity = muted && (level >= userSpeakingLevel) ? 1.0 : 0.7; + var rectOpacity = (muted && (level >= userSpeakingLevel)) && warnWhenMuted ? 1.0 : 0.7; if (pushToTalk && !pushingToTalk) { rectOpacity = (level >= userSpeakingLevel) ? 1.0 : 0.7; } else if (mouseArea.containsMouse && rectOpacity != 1.0) { @@ -163,7 +164,7 @@ Rectangle { Item { id: status; - visible: pushToTalk || (muted && (level >= userSpeakingLevel)); + visible: pushToTalk || (muted && (level >= userSpeakingLevel) && warnWhenMuted); anchors { left: parent.left; @@ -187,7 +188,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor; + color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted && warnWhenMuted) ? colors.mutedColor : colors.unmutedColor; font.bold: true text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE"); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index bd7e79dffc..e392680df9 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js" + "system/miniTablet.js", + "system/audioMuteOverlay.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index e715e97575..feea604a92 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -58,20 +58,6 @@ parentID: MyAvatar.SELF_ID, parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") }); - } else { - var textDimensions = { x: 100, y: 50 }; - warningOverlayID = Overlays.addOverlay("text", { - name: "Muted-Warning", - font: { size: 36 }, - text: warningText, - x: (Window.innerWidth - textDimensions.x) / 2, - y: (Window.innerHeight - textDimensions.y), - width: textDimensions.x, - height: textDimensions.y, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - visible: true - }); } } @@ -141,4 +127,4 @@ Audio.mutedChanged.connect(startOrStopPoll); Audio.warnWhenMutedChanged.connect(startOrStopPoll); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From 3c65b92ff50127672c271e8e05a84b17c8e1aaa2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 29 Mar 2019 10:41:16 -0700 Subject: [PATCH 66/78] moving text position higher --- scripts/system/audioMuteOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index feea604a92..9acc5ab123 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -43,7 +43,7 @@ if (HMD.active) { warningOverlayID = Overlays.addOverlay("text3d", { name: "Muted-Warning", - localPosition: { x: 0.0, y: -0.5, z: -1.0 }, + localPosition: { x: 0.0, y: -0.45, z: -1.0 }, localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), text: warningText, textAlpha: 1, From 26224087924d765da046035b8f4ed4df6de5595f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 29 Mar 2019 10:45:40 -0700 Subject: [PATCH 67/78] warn when muted only affects hmd --- interface/resources/qml/hifi/audio/Audio.qml | 4 ++-- interface/resources/qml/hifi/audio/MicBarApplication.qml | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 8bec821f34..f7e2494813 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -228,7 +228,7 @@ Rectangle { anchors.top: noiseReductionSwitch.bottom anchors.topMargin: 24 anchors.left: parent.left - labelTextOn: qsTr("Push To Talk (T)"); + labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk"); labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; @@ -254,7 +254,7 @@ Rectangle { switchWidth: root.switchWidth; anchors.top: parent.top anchors.left: parent.left - labelTextOn: qsTr("Warn when muted"); + labelTextOn: qsTr("Warn when muted in HMD"); labelTextSize: 16; backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.warnWhenMuted; diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 4e0adfd95c..70bded0fc6 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -18,7 +18,6 @@ import TabletScriptingInterface 1.0 Rectangle { id: micBar; readonly property var level: AudioScriptingInterface.inputLevel; - readonly property var warnWhenMuted: AudioScriptingInterface.warnWhenMuted; readonly property var clipping: AudioScriptingInterface.clipping; property var muted: AudioScriptingInterface.muted; property var pushToTalk: AudioScriptingInterface.pushToTalk; @@ -55,7 +54,7 @@ Rectangle { opacity: 0.7; onLevelChanged: { - var rectOpacity = (muted && (level >= userSpeakingLevel)) && warnWhenMuted ? 1.0 : 0.7; + var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7; if (pushToTalk && !pushingToTalk) { rectOpacity = (level >= userSpeakingLevel) ? 1.0 : 0.7; } else if (mouseArea.containsMouse && rectOpacity != 1.0) { @@ -164,7 +163,7 @@ Rectangle { Item { id: status; - visible: pushToTalk || (muted && (level >= userSpeakingLevel) && warnWhenMuted); + visible: pushToTalk || (muted && (level >= userSpeakingLevel); anchors { left: parent.left; @@ -188,7 +187,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted && warnWhenMuted) ? colors.mutedColor : colors.unmutedColor; + color: pushToTalk ? (pushingToTalk ? colors.unmutedColor : colors.mutedColor) : (level >= userSpeakingLevel && muted) ? colors.mutedColor : colors.unmutedColor; font.bold: true text: pushToTalk ? (HMD.active ? "PTT" : "PTT-(T)") : (muted ? "MUTED" : "MUTE"); From db17f094dab32fdd822eefbd77c32e26d582a187 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Fri, 29 Mar 2019 15:23:16 -0700 Subject: [PATCH 68/78] adding entities to fade out list; --- interface/src/avatar/AvatarManager.cpp | 5 +- .../src/avatars-renderer/Avatar.cpp | 4 +- .../src/avatars-renderer/Avatar.h | 2 +- .../src/EntityTreeRenderer.cpp | 50 +++++++++++++------ .../src/EntityTreeRenderer.h | 6 ++- .../src/RenderableEntityItem.cpp | 8 ++- libraries/render/src/render/Scene.cpp | 6 +-- libraries/render/src/render/Scene.h | 3 +- 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index aa1847f64b..fea0652964 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -531,7 +531,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar // it might not fire until after we create a new instance for the same remote avatar, which creates a race // on the creation of entities for that avatar instance and the deletion of entities for this instance avatar->removeAvatarEntitiesFromTree(); - + avatar->setIsFading(false); if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { emit DependencyManager::get()->enteredIgnoreRadius(); } else if (removalReason == KillAvatarReason::AvatarDisconnected) { @@ -540,12 +540,11 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar DependencyManager::get()->avatarDisconnected(avatar->getSessionUUID()); render::Transaction transaction; auto scene = qApp->getMain3DScene(); - avatar->fadeOut(scene, removalReason); + avatar->fadeOut(transaction, removalReason); transaction.transitionFinishedOperator(avatar->getRenderItemID(), [avatar]() { avatar->setIsFading(false); }); - scene->enqueueTransaction(transaction); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 96a545fa97..dccf37c5b8 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -659,9 +659,8 @@ void Avatar::fadeIn(render::ScenePointer scene) { scene->enqueueTransaction(transaction); } -void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) { +void Avatar::fadeOut(render::Transaction& transaction, KillAvatarReason reason) { render::Transition::Type transitionType = render::Transition::USER_LEAVE_DOMAIN; - render::Transaction transaction; if (reason == KillAvatarReason::YourAvatarEnteredTheirBubble) { transitionType = render::Transition::BUBBLE_ISECT_TRESPASSER; @@ -669,7 +668,6 @@ void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) { transitionType = render::Transition::BUBBLE_ISECT_OWNER; } fade(transaction, transitionType); - scene->enqueueTransaction(transaction); } void Avatar::fade(render::Transaction& transaction, render::Transition::Type type) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index da04c4adf7..b4cad4f967 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -461,7 +461,7 @@ public: bool isMoving() const { return _moving; } void fadeIn(render::ScenePointer scene); - void fadeOut(render::ScenePointer scene, KillAvatarReason reason); + void fadeOut(render::Transaction& transaction, KillAvatarReason reason); bool isFading() const { return _isFading; } void setIsFading(bool isFading) { _isFading = isFading; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 3eed625916..2701467a2d 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -214,6 +214,30 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { } } +void EntityTreeRenderer::removeFadedRenderables() { + if (_entityRenderablesToFadeOut.empty()) { + return; + } + + std::unique_lock lock(_entitiesToFadeLock); + auto entityIter = _entityRenderablesToFadeOut.begin(); + auto scene = _viewState->getMain3DScene(); + render::Transaction transaction; + + while (entityIter != _entityRenderablesToFadeOut.end()) { + auto entityRenderable = *entityIter; + + if (!entityRenderable->getIsFading()) { + entityRenderable->removeFromScene(scene, transaction); + entityIter = _entityRenderablesToFadeOut.erase(entityIter); + } else { + ++entityIter; + } + } + + scene->enqueueTransaction(transaction); +} + void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { stopDomainAndNonOwnedEntities(); @@ -221,17 +245,15 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { // remove all entities from the scene auto scene = _viewState->getMain3DScene(); if (scene) { - render::Transaction transaction; for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const EntityItemPointer& entityItem = renderer->getEntity(); if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { - renderer->removeFromScene(scene, transaction); + fadeOutRenderable(renderer); } else { savedEntities[entry.first] = entry.second; } } - scene->enqueueTransaction(transaction); } _renderablesToUpdate = savedEntities; @@ -258,12 +280,10 @@ void EntityTreeRenderer::clear() { // remove all entities from the scene auto scene = _viewState->getMain3DScene(); if (scene) { - render::Transaction transaction; for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; - renderer->removeFromScene(scene, transaction); + fadeOutRenderable(renderer); } - scene->enqueueTransaction(transaction); } else { qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; } @@ -531,6 +551,7 @@ void EntityTreeRenderer::update(bool simulate) { } } + removeFadedRenderables(); } void EntityTreeRenderer::handleSpaceUpdate(std::pair proxyUpdate) { @@ -1016,10 +1037,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities - // here's where we remove the entity payload from the scene - render::Transaction transaction; - renderable->removeFromScene(scene, transaction); - scene->enqueueTransaction(transaction); + fadeOutRenderable(renderable); } void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) { @@ -1057,24 +1075,26 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool } } -void EntityTreeRenderable::fadeOutRenderable(const EntityRendererPointer& renderable) { +void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderable) { render::Transaction transaction; - auto scene = qApp->getMain3DScene(); + auto scene = _viewState->getMain3DScene(); + renderable->setIsFading(true); transaction.transitionFinishedOperator(renderable->getRenderItemID(), [renderable]() { renderable->setIsFading(false); }); scene->enqueueTransaction(transaction); + _entityRenderablesToFadeOut.push_back(renderable); } void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision) { assert((bool)entity); auto renderable = renderableForEntity(entity); - if (!renderable) { - return; + if (!renderable) { + return; } - + SharedSoundPointer collisionSound = renderable->getCollisionSound(); if (!collisionSound) { return; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 4d6c0e3ba2..32504abd56 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -93,7 +93,7 @@ public: /// reloads the entity scripts, calling unload and preload void reloadEntityScripts(); - void fadeOutRenderable(const EntityRenderablePointer& renderable); + void fadeOutRenderable(const EntityRendererPointer& renderable); void removeFadedRenderables(); // event handles which may generate entity related events @@ -258,7 +258,9 @@ private: std::unordered_map _renderablesToUpdate; std::unordered_map _entitiesInScene; std::unordered_map _entitiesToAdd; - std::vector _entityRendersToFadeOut; + + std::mutex _entitiesToFadeLock; + std::vector _entityRenderablesToFadeOut; // For Scene.shouldRenderEntities QList _entityIDsLastInScene; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index a6826da91b..e4e135cd7f 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -148,7 +148,7 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entit }); } -EntityRenderer::~EntityRenderer() { } +EntityRenderer::~EntityRenderer() {} // // Smart payload proxy members, implementing the payload interface @@ -418,9 +418,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa if (fading || _prevIsTransparent != transparent) { emit requestRenderUpdate(); } - if (fading) { - _isFading = Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f; - } + _prevIsTransparent = transparent; updateModelTransformAndBound(); @@ -493,4 +491,4 @@ glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const Puls } return result; -} \ No newline at end of file +} diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index ad6523ce11..c93054d5fe 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -461,8 +461,8 @@ void Scene::resetTransitionFinishedOperator(const Transaction::TransitionFinishe TransitionStage::Index transitionId = item.getTransitionId(); if (!TransitionStage::isIndexInvalid(transitionId)) { _transitionFinishedOperatorMap[transitionId].emplace_back(func); - } else { - fucn(); + } else if (func) { + func(); } } } @@ -559,7 +559,7 @@ void Scene::resetItemTransition(ItemID itemId) { auto transitionStage = getStage(TransitionStage::getName()); auto finishedOperators = _transitionFinishedOperatorMap[transitionId]; - + qDebug() << "removing transition: " << transitionId; for (auto finishedOperator : finishedOperators) { if (finishedOperator) { finishedOperator(); diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index e2195cf8c1..08fbf33bcd 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -34,6 +34,7 @@ class Scene; // of updating the scene before it s rendered. // + class Transaction { friend class Scene; public: @@ -230,7 +231,7 @@ protected: mutable std::mutex _selectionsMutex; // mutable so it can be used in the thread safe getSelection const method SelectionMap _selections; - std::unordered_map> _transitionFinishedOperatorMap; + std::unordered_map> _transitionFinishedOperatorMap; void resetSelections(const Transaction::SelectionResets& transactions); // More actions coming to selections soon: From 426ffe5a142ffeae8d4e28954b12119e70fcb30f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 29 Mar 2019 15:38:42 -0700 Subject: [PATCH 69/78] Fixing typo --- interface/resources/qml/hifi/audio/MicBarApplication.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 70bded0fc6..5a98d03094 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -163,7 +163,7 @@ Rectangle { Item { id: status; - visible: pushToTalk || (muted && (level >= userSpeakingLevel); + visible: pushToTalk || (muted && (level >= userSpeakingLevel)); anchors { left: parent.left; From c4cccc6ef75763ffa1b42df2e382de847de0f8a2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 29 Mar 2019 17:04:44 -0700 Subject: [PATCH 70/78] fix opacity for containing mouse only --- interface/resources/qml/hifi/audio/MicBarApplication.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 5a98d03094..bc3f4dff89 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -56,7 +56,7 @@ Rectangle { onLevelChanged: { var rectOpacity = (muted && (level >= userSpeakingLevel)) ? 1.0 : 0.7; if (pushToTalk && !pushingToTalk) { - rectOpacity = (level >= userSpeakingLevel) ? 1.0 : 0.7; + rectOpacity = (mouseArea.containsMouse) ? 1.0 : 0.7; } else if (mouseArea.containsMouse && rectOpacity != 1.0) { rectOpacity = 1.0; } From 58abfb44c4936664a18299a9d34eb65a76409f55 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 31 Mar 2019 10:35:03 -0700 Subject: [PATCH 71/78] Globally disallow use of the camera or microphone by hosted web content --- interface/src/Application.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 635932ea1c..676534f6db 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2882,11 +2882,19 @@ void Application::initializeGL() { } #if !defined(DISABLE_QML) + QStringList chromiumFlags; + // Bug 21993: disable microphone and camera input + chromiumFlags << "--use-fake-device-for-media-stream"; // Disable signed distance field font rendering on ATI/AMD GPUs, due to // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app std::string vendor{ (const char*)glGetString(GL_VENDOR) }; if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); + chromiumFlags << "--disable-distance-field-text"; + } + + // Ensure all Qt webengine processes launched from us have the appropriate command line flags + if (!chromiumFlags.empty()) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags.join(' ').toLocal8Bit()); } #endif From 7d4e59bfe3a6227ce6405698e4ab5c8fbbe7621d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 31 Mar 2019 12:53:24 -0700 Subject: [PATCH 72/78] Force packet version change --- libraries/networking/src/udt/PacketHeaders.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 413ff14b17..8c76a3ebd0 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -266,6 +266,7 @@ enum class EntityVersion : PacketVersion { ModelScale, ReOrderParentIDProperties, CertificateTypeProperty, + DisableWebMedia, // Add new versions above here NUM_PACKET_TYPE, From 0b822b2cbc2c3cd638027bae6c6099fc688ca22b Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Mon, 1 Apr 2019 18:26:00 +0300 Subject: [PATCH 73/78] Fixed calls to addingWearable() and deletingWearable(). The calls used a parameter type of QUuid instead of EntityItemID. This resulted in the following warnings in the log: [04/01 18:16:11.012] [WARNING] [default] [53296] QMetaObject::invokeMethod: No such method EntityScriptingInterface::addingWearable(QUuid) [04/01 18:16:11.012] [WARNING] [default] [53296] Candidates are: [04/01 18:16:11.012] [WARNING] [default] [53296] addingWearable(EntityItemID) --- libraries/entities/src/EntityScriptingInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index ca914731b5..f8c45b792a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1101,13 +1101,13 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer void EntityScriptingInterface::onAddingEntity(EntityItem* entity) { if (entity->isWearable()) { - QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(QUuid, entity->getEntityItemID())); + QMetaObject::invokeMethod(this, "addingWearable", Q_ARG(EntityItemID, entity->getEntityItemID())); } } void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) { if (entity->isWearable()) { - QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(QUuid, entity->getEntityItemID())); + QMetaObject::invokeMethod(this, "deletingWearable", Q_ARG(EntityItemID, entity->getEntityItemID())); } } From 0a8d195f6f09cdccd157ab0e92bec5f807d616a6 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 1 Apr 2019 11:32:52 -0700 Subject: [PATCH 74/78] adding top padding for my name card --- interface/resources/qml/hifi/NameCard.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 141ddf0077..7e8218b7df 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -129,6 +129,7 @@ Item { height: 40 // Anchors anchors.top: avatarImage.top + anchors.topMargin: avatarImage.visible ? 18 : 0; anchors.left: avatarImage.right anchors.leftMargin: avatarImage.visible ? 5 : 0; anchors.rightMargin: 5; From afdf95c894de5302d2213288dff9c134b35042ce Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 1 Apr 2019 12:20:55 -0700 Subject: [PATCH 75/78] remove unused slotted function --- interface/src/Application.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 734eb7221b..ecafbfdb2c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1295,21 +1295,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setCrashAnnotation("display_plugin", displayPlugin->getName().toStdString()); setCrashAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0"); }); - connect(this, &Application::activeDisplayPluginChanged, this, [&](){ -#if !defined(Q_OS_ANDROID) - if (!getLoginDialogPoppedUp() && _desktopRootItemCreated) { -/* if (isHMDMode()) {*/ - //createAvatarInputsBar(); - //auto offscreenUi = getOffscreenUI(); - //offscreenUi->hide(AVATAR_INPUTS_BAR_QML.toString()); - //} else { - //destroyAvatarInputsBar(); - //auto offscreenUi = getOffscreenUI(); - //offscreenUi->show(AVATAR_INPUTS_BAR_QML.toString(), "AvatarInputsBar"); - /*}*/ - } -#endif - }); connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode); connect(this, &Application::activeDisplayPluginChanged, this, [&](){ if (getLoginDialogPoppedUp()) { From 8df34e5d5973713cd3a067a4d871dd725aadbd3f Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 1 Apr 2019 14:50:16 -0700 Subject: [PATCH 76/78] fixing on clicked for audio mic bar --- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index d161f12d70..100f07fe1a 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -87,7 +87,7 @@ Rectangle { if (pushToTalk) { return; } - muted = !muted; + AudioScriptingInterface.muted = !muted; Tablet.playSound(TabletEnums.ButtonClick); } drag.target: dragTarget; From fcb838a71f6d6e2c3f1b94051b131dbcd528f37d Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 1 Apr 2019 15:43:04 -0700 Subject: [PATCH 77/78] addressing review requests --- interface/src/avatar/AvatarManager.cpp | 59 +++++++------------ interface/src/avatar/AvatarManager.h | 4 -- libraries/avatars/src/AvatarHashMap.cpp | 1 - .../src/EntityTreeRenderer.cpp | 31 ++-------- .../src/EntityTreeRenderer.h | 2 - libraries/render/src/render/Scene.cpp | 1 - 6 files changed, 24 insertions(+), 74 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index fea0652964..956ea12aee 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -84,7 +84,6 @@ AvatarManager::AvatarManager(QObject* parent) : AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { AvatarSharedPointer avatar = AvatarHashMap::addAvatar(sessionUUID, mixerWeakPointer); - const auto otherAvatar = std::static_pointer_cast(avatar); if (otherAvatar && _space) { std::unique_lock lock(_spaceLock); @@ -210,7 +209,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { { // lock the hash for read to check the size QReadLocker lock(&_hashLock); - if (_avatarHash.size() < 2 && _avatarsToFadeOut.empty()) { + if (_avatarHash.size() < 2) { return; } } @@ -375,18 +374,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { qApp->getMain3DScene()->enqueueTransaction(renderTransaction); } - if (!_spaceProxiesToDelete.empty() && _space) { - std::unique_lock lock(_spaceLock); - workloadTransaction.remove(_spaceProxiesToDelete); - _spaceProxiesToDelete.clear(); - } _space->enqueueTransaction(workloadTransaction); _numAvatarsUpdated = numAvatarsUpdated; _numAvatarsNotUpdated = numAvatarsNotUpdated; _numHeroAvatarsUpdated = numHerosUpdated; - removeFadedAvatars(); _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; } @@ -399,30 +392,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen } } -void AvatarManager::removeFadedAvatars() { - if (_avatarsToFadeOut.empty()) { - return; - } - - QReadLocker locker(&_hashLock); - auto avatarItr = _avatarsToFadeOut.begin(); - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - while (avatarItr != _avatarsToFadeOut.end()) { - auto avatar = std::static_pointer_cast(*avatarItr); - if (!avatar->isFading()) { - // fading to zero is such a rare event we push a unique transaction for each - if (avatar->isInScene()) { - avatar->removeFromScene(*avatarItr, scene, transaction); - } - avatarItr = _avatarsToFadeOut.erase(avatarItr); - } else { - ++avatarItr; - } - } - scene->enqueueTransaction(transaction); -} - AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { auto otherAvatar = new OtherAvatar(qApp->thread()); otherAvatar->setSessionUUID(sessionUUID); @@ -517,10 +486,6 @@ void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities) void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { auto avatar = std::static_pointer_cast(removedAvatar); - { - std::unique_lock lock(_spaceLock); - _spaceProxiesToDelete.push_back(avatar->getSpaceIndex()); - } AvatarHashMap::handleRemovedAvatar(avatar, removalReason); avatar->tearDownGrabs(); @@ -534,6 +499,15 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar avatar->setIsFading(false); if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { emit DependencyManager::get()->enteredIgnoreRadius(); + + workload::Transaction workloadTransaction; + workloadTransaction.remove(avatar->getSpaceIndex()); + _space->enqueueTransaction(workloadTransaction); + + const render::ScenePointer& scene = qApp->getMain3DScene(); + render::Transaction transaction; + avatar->removeFromScene(avatar, scene, transaction); + scene->enqueueTransaction(transaction); } else if (removalReason == KillAvatarReason::AvatarDisconnected) { // remove from node sets, if present DependencyManager::get()->removeFromIgnoreMuteSets(avatar->getSessionUUID()); @@ -542,13 +516,20 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar auto scene = qApp->getMain3DScene(); avatar->fadeOut(transaction, removalReason); - transaction.transitionFinishedOperator(avatar->getRenderItemID(), [avatar]() { + workload::SpacePointer space = _space; + transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() { avatar->setIsFading(false); + const render::ScenePointer& scene = qApp->getMain3DScene(); + render::Transaction transaction; + avatar->removeFromScene(avatar, scene, transaction); + scene->enqueueTransaction(transaction); + + workload::Transaction workloadTransaction; + workloadTransaction.remove(avatar->getSpaceIndex()); + space->enqueueTransaction(workloadTransaction); }); scene->enqueueTransaction(transaction); } - - _avatarsToFadeOut.push_back(removedAvatar); } void AvatarManager::clearOtherAvatars() { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 9dde3a11fb..f9b82da0c1 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -222,8 +222,6 @@ private: AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override; - void removeFadedAvatars(); - // called only from the AvatarHashMap thread - cannot be called while this thread holds the // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree // frequently grabs a read lock on the hash to get a given avatar by ID @@ -231,7 +229,6 @@ private: KillAvatarReason removalReason = KillAvatarReason::NoReason) override; void handleTransitAnimations(AvatarTransit::Status status); - std::vector _avatarsToFadeOut; using SetOfOtherAvatars = std::set; SetOfOtherAvatars _avatarsToChangeInPhysics; @@ -251,7 +248,6 @@ private: mutable std::mutex _spaceLock; workload::SpacePointer _space; - std::vector _spaceProxiesToDelete; AvatarTransit::TransitConfig _transitConfig; }; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c16d65506a..3abd352778 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -439,7 +439,6 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo } auto removedAvatar = _avatarHash.take(sessionUUID); - if (removedAvatar) { removedAvatars.push_back(removedAvatar); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 2701467a2d..7c9c724212 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -214,30 +214,6 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { } } -void EntityTreeRenderer::removeFadedRenderables() { - if (_entityRenderablesToFadeOut.empty()) { - return; - } - - std::unique_lock lock(_entitiesToFadeLock); - auto entityIter = _entityRenderablesToFadeOut.begin(); - auto scene = _viewState->getMain3DScene(); - render::Transaction transaction; - - while (entityIter != _entityRenderablesToFadeOut.end()) { - auto entityRenderable = *entityIter; - - if (!entityRenderable->getIsFading()) { - entityRenderable->removeFromScene(scene, transaction); - entityIter = _entityRenderablesToFadeOut.erase(entityIter); - } else { - ++entityIter; - } - } - - scene->enqueueTransaction(transaction); -} - void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { stopDomainAndNonOwnedEntities(); @@ -551,7 +527,6 @@ void EntityTreeRenderer::update(bool simulate) { } } - removeFadedRenderables(); } void EntityTreeRenderer::handleSpaceUpdate(std::pair proxyUpdate) { @@ -1080,12 +1055,14 @@ void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderab auto scene = _viewState->getMain3DScene(); renderable->setIsFading(true); - transaction.transitionFinishedOperator(renderable->getRenderItemID(), [renderable]() { + transaction.transitionFinishedOperator(renderable->getRenderItemID(), [scene, renderable]() { renderable->setIsFading(false); + render::Transaction transaction; + renderable->removeFromScene(scene, transaction); + scene->enqueueTransaction(transaction); }); scene->enqueueTransaction(transaction); - _entityRenderablesToFadeOut.push_back(renderable); } void EntityTreeRenderer::playEntityCollisionSound(const EntityItemPointer& entity, const Collision& collision) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 32504abd56..08dd06f5c1 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -259,8 +259,6 @@ private: std::unordered_map _entitiesInScene; std::unordered_map _entitiesToAdd; - std::mutex _entitiesToFadeLock; - std::vector _entityRenderablesToFadeOut; // For Scene.shouldRenderEntities QList _entityIDsLastInScene; diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index c93054d5fe..16d1d49d9f 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -559,7 +559,6 @@ void Scene::resetItemTransition(ItemID itemId) { auto transitionStage = getStage(TransitionStage::getName()); auto finishedOperators = _transitionFinishedOperatorMap[transitionId]; - qDebug() << "removing transition: " << transitionId; for (auto finishedOperator : finishedOperators) { if (finishedOperator) { finishedOperator(); From 8115ef6d3613dfe228d201d145759f96e77c45dd Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 1 Apr 2019 16:46:15 -0700 Subject: [PATCH 78/78] addressing more requested changes --- interface/src/avatar/AvatarManager.cpp | 2 -- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 1 - libraries/avatars-renderer/src/avatars-renderer/Avatar.h | 3 --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 -- libraries/entities-renderer/src/EntityTreeRenderer.h | 1 - libraries/entities-renderer/src/RenderableEntityItem.cpp | 3 +++ libraries/entities-renderer/src/RenderableEntityItem.h | 3 --- 7 files changed, 3 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 956ea12aee..3fbff292d7 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -496,7 +496,6 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar // it might not fire until after we create a new instance for the same remote avatar, which creates a race // on the creation of entities for that avatar instance and the deletion of entities for this instance avatar->removeAvatarEntitiesFromTree(); - avatar->setIsFading(false); if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { emit DependencyManager::get()->enteredIgnoreRadius(); @@ -518,7 +517,6 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar workload::SpacePointer space = _space; transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() { - avatar->setIsFading(false); const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; avatar->removeFromScene(avatar, scene, transaction); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index dccf37c5b8..77e5933d3d 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -677,7 +677,6 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ transaction.addTransitionToItem(itemId, type, _renderItemID); } } - _isFading = true; } void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b4cad4f967..56684e34b3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -462,8 +462,6 @@ public: void fadeIn(render::ScenePointer scene); void fadeOut(render::Transaction& transaction, KillAvatarReason reason); - bool isFading() const { return _isFading; } - void setIsFading(bool isFading) { _isFading = isFading; } // JSDoc is in AvatarData.h. Q_INVOKABLE virtual float getEyeHeight() const override; @@ -655,7 +653,6 @@ protected: bool _initialized { false }; bool _isAnimatingScale { false }; bool _mustFadeIn { false }; - bool _isFading { false }; bool _reconstructSoftEntitiesJointMap { false }; float _modelScale { 1.0f }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7c9c724212..6cfff7bc41 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1054,9 +1054,7 @@ void EntityTreeRenderer::fadeOutRenderable(const EntityRendererPointer& renderab render::Transaction transaction; auto scene = _viewState->getMain3DScene(); - renderable->setIsFading(true); transaction.transitionFinishedOperator(renderable->getRenderItemID(), [scene, renderable]() { - renderable->setIsFading(false); render::Transaction transaction; renderable->removeFromScene(scene, transaction); scene->enqueueTransaction(transaction); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 08dd06f5c1..cee91ad1c7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -94,7 +94,6 @@ public: void reloadEntityScripts(); void fadeOutRenderable(const EntityRendererPointer& renderable); - void removeFadedRenderables(); // event handles which may generate entity related events QUuid mousePressEvent(QMouseEvent* event); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index e4e135cd7f..3a56521702 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -418,6 +418,9 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa if (fading || _prevIsTransparent != transparent) { emit requestRenderUpdate(); } + if (fading) { + _isFading = Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f; + } _prevIsTransparent = transparent; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index b37e46d02e..39f9ad091e 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -44,9 +44,6 @@ public: const EntityItemPointer& getEntity() const { return _entity; } const ItemID& getRenderItemID() const { return _renderItemID; } - bool getIsFading() { return _isFading; } - void setIsFading(bool isFading) { _isFading = isFading; } - const SharedSoundPointer& getCollisionSound() { return _collisionSound; } void setCollisionSound(const SharedSoundPointer& sound) { _collisionSound = sound; }