diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 548f61e1de..ddb0743bf8 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/icons/tablet-icons/mic-mute-a.svg b/interface/resources/icons/tablet-icons/mic-mute-a.svg index 4b199c8e01..9dc2c53443 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-a.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-a.svg @@ -1,70 +1,25 @@ - + - -image/svg+xml \ No newline at end of file + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/mic-mute-i.svg b/interface/resources/icons/tablet-icons/mic-mute-i.svg index 69feec7c17..9dc2c53443 100644 --- a/interface/resources/icons/tablet-icons/mic-mute-i.svg +++ b/interface/resources/icons/tablet-icons/mic-mute-i.svg @@ -1,21 +1,25 @@ - + + + - - - + + - - + diff --git a/interface/resources/qml/AvatarInputs.qml b/interface/resources/qml/AvatarInputs.qml index 28f3c0c7b9..d86d4fd022 100644 --- a/interface/resources/qml/AvatarInputs.qml +++ b/interface/resources/qml/AvatarInputs.qml @@ -12,84 +12,21 @@ import QtQuick.Controls 1.3 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 +import "./hifi/audio" as HifiAudio + Hifi.AvatarInputs { - id: root + id: root; objectName: "AvatarInputs" - width: rootWidth - height: controls.height - x: 10; y: 5 + width: audio.width; + height: audio.height; + x: 10; y: 5; - readonly property int rootWidth: 265 - readonly property int iconSize: 24 - readonly property int iconPadding: 5 + readonly property bool shouldReposition: true; - readonly property bool shouldReposition: true - - Settings { - category: "Overlay.AvatarInputs" - property alias x: root.x - property alias y: root.y - } - - MouseArea { - id: hover - hoverEnabled: true - drag.target: parent - anchors.fill: parent - } - - Item { - id: controls - width: root.rootWidth - height: 44 - visible: root.showAudioTools - - Rectangle { - anchors.fill: parent - color: "#00000000" - - Item { - id: audioMeter - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: root.iconPadding - anchors.right: parent.right - anchors.rightMargin: root.iconPadding - height: 8 - Rectangle { - id: blueRect - color: "blue" - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - width: parent.width / 4 - } - Rectangle { - id: greenRect - color: "green" - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: blueRect.right - anchors.right: redRect.left - } - Rectangle { - id: redRect - color: "red" - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - width: parent.width / 5 - } - Rectangle { - z: 100 - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - width: (1.0 - root.audioLevel) * parent.width - color: "black" - } - } - } + HifiAudio.MicBar { + id: audio; + visible: root.showAudioTools; + standalone: true; + dragTarget: parent; } } - diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 9ef151b32e..9a84418b3a 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -65,6 +65,12 @@ Windows.Window { root.dynamicContent.fromScript(message); } } + + function clearDebugWindow() { + if (root.dynamicContent && root.dynamicContent.clearWindow) { + root.dynamicContent.clearWindow(); + } + } // Handle message traffic from our loaded QML to the script that launched us signal sendToScript(var message); diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml index d6dc5d2736..a637207781 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -22,6 +22,7 @@ Original.CheckBox { readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light property bool isRedCheck: false property int boxSize: 14 + property bool wrap: true; readonly property int boxRadius: 3 readonly property int checkSize: Math.max(boxSize - 8, 10) readonly property int checkRadius: 2 @@ -92,7 +93,8 @@ Original.CheckBox { text: control.text color: control.color x: 2 - wrapMode: Text.Wrap + wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap + elide: checkBox.wrap ? Text.ElideNone : Text.ElideRight enabled: checkBox.enabled } } diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 929752f925..c13bd3281a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -52,33 +52,40 @@ Rectangle { Separator { visible: root.showTitle() } - Grid { - columns: 2; + ColumnLayout { x: 16; // padding does not work spacing: 16; - AudioControls.CheckBox { - text: qsTr("Mute microphone"); - checked: Audio.muted; - onClicked: { - Audio.muted = checked; - checked = Qt.binding(function() { return Audio.muted; }); // restore binding + // mute is in its own row + RowLayout { + AudioControls.CheckBox { + text: qsTr("Mute microphone"); + isRedCheck: true; + checked: Audio.muted; + onClicked: { + Audio.muted = checked; + checked = Qt.binding(function() { return Audio.muted; }); // restore binding + } } } - AudioControls.CheckBox { - text: qsTr("Enable noise reduction"); - checked: Audio.noiseReduction; - onClicked: { - Audio.noiseReduction = checked; - checked = Qt.binding(function() { return Audio.noiseReduction; }); // restore binding + + RowLayout { + spacing: 16; + AudioControls.CheckBox { + text: qsTr("Enable noise reduction"); + checked: Audio.noiseReduction; + onClicked: { + Audio.noiseReduction = checked; + checked = Qt.binding(function() { return Audio.noiseReduction; }); // restore binding + } } - } - AudioControls.CheckBox { - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + AudioControls.CheckBox { + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } } } } @@ -110,12 +117,25 @@ Rectangle { delegate: Item { width: parent.width; height: 36; - AudioControls.CheckBox { - text: display; - checked: selected; - onClicked: { - selected = checked; - checked = Qt.binding(function() { return selected; }); // restore binding + + RowLayout { + width: parent.width; + + AudioControls.CheckBox { + Layout.maximumWidth: parent.width - level.width - 40; + text: display; + wrap: false; + checked: selected; + onClicked: { + selected = checked; + checked = Qt.binding(function() { return selected; }); // restore binding + } + } + InputLevel { + id: level; + Layout.alignment: Qt.AlignRight; + Layout.rightMargin: 30; + visible: selected; } } } @@ -124,17 +144,23 @@ Rectangle { Separator {} RowLayout { - HiFiGlyphs { - text: hifi.glyphs.unmuted; - color: hifi.colors.primaryHighlight; - anchors.verticalCenter: parent.verticalCenter; - size: 36; - } - RalewayRegular { - anchors.verticalCenter: parent.verticalCenter; - size: 16; - color: hifi.colors.lightGrayText; - text: qsTr("CHOOSE OUTPUT DEVICE"); + Column { + RowLayout { + HiFiGlyphs { + text: hifi.glyphs.unmuted; + color: hifi.colors.primaryHighlight; + anchors.verticalCenter: parent.verticalCenter; + size: 36; + } + RalewayRegular { + anchors.verticalCenter: parent.verticalCenter; + size: 16; + color: hifi.colors.lightGrayText; + text: qsTr("CHOOSE OUTPUT DEVICE"); + } + } + + PlaySampleSound { anchors { left: parent.left; leftMargin: 60 }} } } diff --git a/interface/resources/qml/hifi/audio/InputLevel.qml b/interface/resources/qml/hifi/audio/InputLevel.qml new file mode 100644 index 0000000000..58ce3a1ec7 --- /dev/null +++ b/interface/resources/qml/hifi/audio/InputLevel.qml @@ -0,0 +1,105 @@ +// +// InputLevel.qml +// qml/hifi/audio +// +// Created by Zach Pomerantz on 6/20/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 QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 + +Rectangle { + readonly property var level: Audio.inputLevel; + + width: 70; + height: 8; + + color: "transparent"; + + Item { + id: colors; + + readonly property string muted: "#E2334D"; + readonly property string gutter: "#575757"; + readonly property string greenStart: "#39A38F"; + readonly property string greenEnd: "#1FC6A6"; + readonly property string red: colors.muted; + } + + Text { + id: status; + + anchors { + horizontalCenter: parent.horizontalCenter; + verticalCenter: parent.verticalCenter; + } + + visible: Audio.muted; + color: colors.muted; + + text: "MUTED"; + font.pointSize: 10; + } + + Item { + id: bar; + + width: parent.width; + height: parent.height; + + anchors { fill: parent } + + visible: !status.visible; + + Rectangle { // base + radius: 4; + anchors { fill: parent } + color: colors.gutter; + } + + Rectangle { // mask + id: mask; + width: parent.width * level; + radius: 5; + anchors { + bottom: parent.bottom; + bottomMargin: 0; + top: parent.top; + topMargin: 0; + left: parent.left; + leftMargin: 0; + } + } + + LinearGradient { + anchors { fill: mask } + source: mask + start: Qt.point(0, 0); + end: Qt.point(70, 0); + gradient: Gradient { + GradientStop { + position: 0; + color: colors.greenStart; + } + GradientStop { + position: 0.8; + color: colors.greenEnd; + } + GradientStop { + position: 0.801; + color: colors.red; + } + GradientStop { + position: 1; + color: colors.red; + } + } + } + } +} diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml new file mode 100644 index 0000000000..10e12551b7 --- /dev/null +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -0,0 +1,223 @@ +// +// 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 QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 + +Rectangle { + readonly property var level: Audio.inputLevel; + + property bool standalone: false; + property var dragTarget: null; + + width: 240; + height: 50; + + radius: 5; + + color: "#00000000"; + border { + width: (standalone || Audio.muted || mouseArea.containsMouse) ? 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: 236; + height: 46; + + 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: { Audio.muted = !Audio.muted; } + drag.target: dragTarget; + } + + Item { + 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 red: colors.muted; + readonly property string fill: "#55000000"; + readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; + readonly property string icon: (Audio.muted && !mouseArea.containsMouse) ? muted : unmuted; + } + + Item { + id: icon; + + anchors { + left: parent.left; + leftMargin: 5; + 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"; + + function exclusiveOr(a, b) { return (a || b) && !(a && b); } + + id: image; + source: exclusiveOr(Audio.muted, mouseArea.containsMouse) ? mutedIcon : unmutedIcon; + + width: 30; + height: 30; + anchors { + left: parent.left; + leftMargin: 5; + top: parent.top; + topMargin: 5; + } + } + + ColorOverlay { + anchors { fill: image } + source: image; + color: colors.icon; + } + } + } + + Item { + id: status; + + readonly property string color: (Audio.muted && !mouseArea.containsMouse) ? colors.muted : colors.unmuted; + + visible: Audio.muted || mouseArea.containsMouse; + + anchors { + left: parent.left; + leftMargin: 50; + verticalCenter: parent.verticalCenter; + } + + width: 170; + height: 8 + + Text { + anchors { + horizontalCenter: parent.horizontalCenter; + verticalCenter: parent.verticalCenter; + } + + color: parent.color; + + text: Audio.muted ? (mouseArea.containsMouse ? "UNMUTE" : "MUTED") : "MUTE"; + font.pointSize: 12; + } + + Rectangle { + anchors { + left: parent.left; + verticalCenter: parent.verticalCenter; + } + + width: 50; + height: 4; + color: parent.color; + } + + Rectangle { + anchors { + right: parent.right; + verticalCenter: parent.verticalCenter; + } + + width: 50; + height: 4; + color: parent.color; + } + } + + Item { + id: bar; + + visible: !status.visible; + + anchors.fill: status; + + width: status.width; + + Rectangle { // base + radius: 4; + anchors { fill: parent } + color: colors.gutter; + } + + Rectangle { // mask + id: mask; + width: parent.width * level; + radius: 5; + anchors { + bottom: parent.bottom; + bottomMargin: 0; + top: parent.top; + topMargin: 0; + left: parent.left; + leftMargin: 0; + } + } + + LinearGradient { + anchors { fill: mask } + source: mask + start: Qt.point(0, 0); + end: Qt.point(170, 0); + gradient: Gradient { + GradientStop { + position: 0; + color: colors.greenStart; + } + GradientStop { + position: 0.8; + color: colors.greenEnd; + } + GradientStop { + position: 0.81; + color: colors.red; + } + GradientStop { + position: 1; + color: colors.red; + } + } + } + } +} diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml new file mode 100644 index 0000000000..99f3648ec3 --- /dev/null +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -0,0 +1,86 @@ +// +// PlaySampleSound.qml +// qml/hifi/audio +// +// Created by Zach Pomerantz on 6/13/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 QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 + +import "../../styles-uit" +import "../../controls-uit" as HifiControls + +RowLayout { + property var sound: null; + property var sample: null; + property bool isPlaying: false; + function createSampleSound() { + var SOUND = Qt.resolvedUrl("../../../sounds/sample.wav"); + sound = SoundCache.getSound(SOUND); + sample = null; + } + function playSound() { + // FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap + // FIXME: Audio.playSystemSound should not require position + sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); + isPlaying = true; + sample.finished.connect(function() { isPlaying = false; sample = null; }); + } + function stopSound() { + sample && sample.stop(); + } + + Component.onCompleted: createSampleSound(); + Component.onDestruction: stopSound(); + onVisibleChanged: { + if (!visible) { + stopSound(); + } + } + + HifiConstants { id: hifi; } + + Button { + style: ButtonStyle { + background: Rectangle { + implicitWidth: 20; + implicitHeight: 20; + radius: hifi.buttons.radius; + gradient: Gradient { + GradientStop { + position: 0.2; + color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; + } + GradientStop { + position: 1.0; + color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; + } + } + } + label: HiFiGlyphs { + // absolutely position due to asymmetry in glyph + x: isPlaying ? 0 : 1; + y: 1; + size: 14; + color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white"; + text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; + } + } + onClicked: isPlaying ? stopSound() : playSound(); + } + + RalewayRegular { + Layout.leftMargin: 2; + size: 14; + color: "white"; + text: isPlaying ? qsTr("Stop sample sound") : qsTr("Play sample sound"); + } + +} diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml index 25cbd02426..43672190dd 100644 --- a/interface/resources/qml/hifi/tablet/Tablet.qml +++ b/interface/resources/qml/hifi/tablet/Tablet.qml @@ -1,20 +1,16 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 + import "../../styles-uit" +import "../audio" as HifiAudio Item { id: tablet objectName: "tablet" - property double micLevel: 0.8 property int rowIndex: 0 property int columnIndex: 0 property int count: (flowMain.children.length - 1) - // called by C++ code to keep audio bar updated - function setMicLevel(newMicLevel) { - tablet.micLevel = newMicLevel; - } - // used to look up a button by its uuid function findButtonIndex(uuid) { if (!uuid) { @@ -83,6 +79,16 @@ Item { Rectangle { id: bgTopBar height: 90 + + anchors { + top: parent.top + topMargin: 0 + left: parent.left + leftMargin: 0 + right: parent.right + rightMargin: 0 + } + gradient: Gradient { GradientStop { position: 0 @@ -94,108 +100,13 @@ Item { color: "#1e1e1e" } } - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.topMargin: 0 - anchors.top: parent.top - Item { - id: audioIcon - anchors.verticalCenter: parent.verticalCenter - width: 40 - height: 40 - anchors.left: parent.left - anchors.leftMargin: 5 - - Image { - id: micIcon - source: "../../../icons/tablet-icons/mic.svg" - } - - Item { - visible: (Audio.muted && !toggleMuteMouseArea.containsMouse) - || (!Audio.muted && toggleMuteMouseArea.containsMouse) - - Image { - id: muteIcon - source: "../../../icons/tablet-icons/mic-mute.svg" - } - - ColorOverlay { - anchors.fill: muteIcon - source: muteIcon - color: toggleMuteMouseArea.containsMouse ? "#a0a0a0" : "#ff0000" - } - } - } - - Item { - id: audioBar - width: 170 - height: 10 - anchors.left: parent.left - anchors.leftMargin: 50 - anchors.verticalCenter: parent.verticalCenter - Rectangle { - id: audioBarBase - color: "#333333" - radius: 5 - anchors.fill: parent - } - Rectangle { - id: audioBarMask - width: parent.width * tablet.micLevel - color: "#333333" - radius: 5 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - } - LinearGradient { - anchors.fill: audioBarMask - source: audioBarMask - start: Qt.point(0, 0) - end: Qt.point(170, 0) - gradient: Gradient { - GradientStop { - position: 0 - color: "#2c8e72" - } - GradientStop { - position: 0.8 - color: "#1fc6a6" - } - GradientStop { - position: 0.81 - color: "#ea4c5f" - } - GradientStop { - position: 1 - color: "#ea4c5f" - } - } - } - } - - MouseArea { - id: toggleMuteMouseArea + HifiAudio.MicBar { anchors { - left: audioIcon.left - right: audioBar.right - top: audioIcon.top - bottom: audioIcon.bottom + left: parent.left + leftMargin: 30 + verticalCenter: parent.verticalCenter } - - hoverEnabled: true - preventStealing: true - propagateComposedEvents: false - scrollGestureEnabled: false - onClicked: { Audio.muted = !Audio.muted } } RalewaySemiBold { @@ -254,27 +165,6 @@ Item { } } - states: [ - State { - name: "muted state" - - PropertyChanges { - target: muteText - text: "UNMUTE" - } - - PropertyChanges { - target: muteIcon - visible: !Audio.muted - } - - PropertyChanges { - target: tablet - micLevel: 0 - } - } - ] - function setCurrentItemState(state) { var index = rowIndex + columnIndex; diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 7b6efbd573..f321f49478 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -333,5 +333,7 @@ Item { readonly property string vol_x_2: "\ue015" readonly property string vol_x_3: "\ue016" readonly property string vol_x_4: "\ue017" + readonly property string playback_play: "\ue01d" + readonly property string stop_square: "\ue01e" } } diff --git a/interface/resources/sounds/sample.wav b/interface/resources/sounds/sample.wav new file mode 100644 index 0000000000..d461ab9186 Binary files /dev/null and b/interface/resources/sounds/sample.wav differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 364c8e3f95..334d80c57c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5569,6 +5569,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get().data(), &ScriptEngines::onErrorMessage); connect(scriptEngine, &ScriptEngine::warningMessage, DependencyManager::get().data(), &ScriptEngines::onWarningMessage); connect(scriptEngine, &ScriptEngine::infoMessage, DependencyManager::get().data(), &ScriptEngines::onInfoMessage); + connect(scriptEngine, &ScriptEngine::clearDebugWindow, DependencyManager::get().data(), &ScriptEngines::onClearDebugWindow); } bool Application::canAcceptURL(const QString& urlString) const { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 752b89bef6..473bcb0de2 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -56,6 +56,7 @@ class MyAvatar : public Avatar { * * @namespace MyAvatar * @augments Avatar + * @property qmlPosition {Vec3} Used as a stopgap for position access by QML, as glm::vec3 is unavailable outside of scripts * @property shouldRenderLocally {bool} Set it to true if you would like to see MyAvatar in your local interface, * and false if you would not like to see MyAvatar in your local interface. * @property motorVelocity {Vec3} Can be used to move the avatar with this velocity. @@ -101,6 +102,10 @@ class MyAvatar : public Avatar { * "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js". */ + // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type + Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) + QVector3D getQmlPosition() { auto p = getPosition(); return QVector3D(p.x, p.y, p.z); } + Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 0b99e60c89..4576190413 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -23,9 +23,33 @@ QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; +float Audio::loudnessToLevel(float loudness) { + const float LOG2 = log(2.0f); + const float METER_LOUDNESS_SCALE = 2.8f / 5.0f; + const float LOG2_LOUDNESS_FLOOR = 11.0f; + + float level = 0.0f; + + loudness += 1.0f; + float log2loudness = logf(loudness) / LOG2; + + if (log2loudness <= LOG2_LOUDNESS_FLOOR) { + level = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE; + } else { + level = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE; + } + + if (level > 1.0f) { + level = 1.0; + } + + return level; +} + Audio::Audio() : _devices(_contextIsHMD) { - auto client = DependencyManager::get(); - connect(client.data(), &AudioClient::muteToggled, this, &Audio::onMutedChanged); + auto client = DependencyManager::get().data(); + connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged); + connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); connect(&_devices._inputs, &AudioDeviceList::deviceChanged, this, &Audio::onInputChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); @@ -88,6 +112,15 @@ void Audio::onInputChanged() { } } +void Audio::onInputLoudnessChanged(float loudness) { + float level = loudnessToLevel(loudness); + + if (_inputLevel != level) { + _inputLevel = level; + emit inputLevelChanged(_inputLevel); + } +} + QString Audio::getContext() const { return _contextIsHMD ? Audio::HMD : Audio::DESKTOP; } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index d438df41ca..953727ede8 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -26,6 +26,7 @@ class Audio : public AudioScriptingInterface { Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) + Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) @@ -34,11 +35,14 @@ public: static QString HMD; static QString DESKTOP; + static float loudnessToLevel(float loudness); + virtual ~Audio() {} bool isMuted() const { return _isMuted; } bool noiseReductionEnabled() const { return _enableNoiseReduction; } float getInputVolume() const { return _inputVolume; } + float getInputLevel() const { return _inputLevel; } QString getContext() const; void setMuted(bool muted); @@ -54,12 +58,14 @@ signals: void mutedChanged(bool isMuted); void noiseReductionChanged(bool isEnabled); void inputVolumeChanged(float volume); + void inputLevelChanged(float level); void contextChanged(const QString& context); public slots: void onMutedChanged(); void onContextChanged(); void onInputChanged(); + void onInputLoudnessChanged(float loudness); protected: // Audio must live on a separate thread from AudioClient to avoid deadlocks @@ -68,6 +74,7 @@ protected: private: float _inputVolume { 1.0f }; + float _inputLevel { 0.0f }; bool _isMuted { false }; bool _enableNoiseReduction; bool _contextIsHMD { false }; diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index 7a46aded77..ccac6d7207 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -45,15 +45,6 @@ AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) { } \ } -#define AI_UPDATE_FLOAT(name, src, epsilon) \ - { \ - float val = src; \ - if (fabsf(_##name - val) >= epsilon) { \ - _##name = val; \ - emit name##Changed(); \ - } \ - } - float AvatarInputs::loudnessToAudioLevel(float loudness) { const float AUDIO_METER_AVERAGING = 0.5; const float LOG2 = log(2.0f); @@ -85,27 +76,6 @@ void AvatarInputs::update() { AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)); AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); AI_UPDATE(isHMD, qApp->isHMDMode()); - - auto audioIO = DependencyManager::get(); - - const float audioLevel = loudnessToAudioLevel(DependencyManager::get()->getLastInputLoudness()); - - AI_UPDATE_FLOAT(audioLevel, audioLevel, 0.01f); - AI_UPDATE(audioClipping, ((audioIO->getTimeSinceLastClip() > 0.0f) && (audioIO->getTimeSinceLastClip() < 1.0f))); - AI_UPDATE(audioMuted, audioIO->isMuted()); - - //// Make muted icon pulsate - //static const float PULSE_MIN = 0.4f; - //static const float PULSE_MAX = 1.0f; - //static const float PULSE_FREQUENCY = 1.0f; // in Hz - //qint64 now = usecTimestampNow(); - //if (now - _iconPulseTimeReference > (qint64)USECS_PER_SECOND) { - // // Prevents t from getting too big, which would diminish glm::cos precision - // _iconPulseTimeReference = now - ((now - _iconPulseTimeReference) % USECS_PER_SECOND); - //} - //float t = (float)(now - _iconPulseTimeReference) / (float)USECS_PER_SECOND; - //float pulseFactor = (glm::cos(t * PULSE_FREQUENCY * 2.0f * PI) + 1.0f) / 2.0f; - //iconColor = PULSE_MIN + (PULSE_MAX - PULSE_MIN) * pulseFactor; } void AvatarInputs::setShowAudioTools(bool showAudioTools) { @@ -124,10 +94,6 @@ void AvatarInputs::toggleCameraMute() { } } -void AvatarInputs::toggleAudioMute() { - DependencyManager::get()->toggleMute(); -} - void AvatarInputs::resetSensors() { qApp->resetSensors(); } diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 247cc54995..fb48df9d73 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -25,9 +25,6 @@ class AvatarInputs : public QQuickItem { AI_PROPERTY(bool, cameraEnabled, false) AI_PROPERTY(bool, cameraMuted, false) - AI_PROPERTY(bool, audioMuted, false) - AI_PROPERTY(bool, audioClipping, false) - AI_PROPERTY(float, audioLevel, 0) AI_PROPERTY(bool, isHMD, false) Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged) @@ -45,16 +42,12 @@ public slots: signals: void cameraEnabledChanged(); void cameraMutedChanged(); - void audioMutedChanged(); - void audioClippingChanged(); - void audioLevelChanged(); void isHMDChanged(); void showAudioToolsChanged(bool show); protected: Q_INVOKABLE void resetSensors(); Q_INVOKABLE void toggleCameraMute(); - Q_INVOKABLE void toggleAudioMute(); private: float _trailingAudioLoudness{ 0 }; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index aa3fefce09..a532e9d8fd 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -51,6 +51,7 @@ #include "avatar/AvatarManager.h" #include "scripting/GlobalServicesScriptingInterface.h" #include "ui/Snapshot.h" +#include "SoundCache.h" static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; @@ -199,6 +200,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index f32d37562a..fc54a04a5e 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1023,6 +1023,8 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) { emit inputReceived(audioBuffer); } + emit inputLoudnessChanged(_lastInputLoudness); + // state machine to detect gate opening and closing bool audioGateOpen = (_lastInputLoudness != 0.0f); bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 01241938d9..54ce3aa6c2 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -210,6 +210,7 @@ signals: bool muteToggled(); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); + void inputLoudnessChanged(float loudness); void outputBytesToNetwork(int numBytes); void inputBytesFromNetwork(int numBytes); void noiseGateOpened(); diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index b5dd60d03b..ecaffaf35c 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -11,6 +11,8 @@ #include "AudioScriptingInterface.h" +#include + #include "ScriptAudioInjector.h" #include "ScriptEngineLogging.h" @@ -19,6 +21,13 @@ void registerAudioMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); } +ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer sound, const QVector3D& position) { + AudioInjectorOptions options; + options.position = glm::vec3(position.x(), position.y(), position.z()); + options.localOnly = true; + return playSound(sound, options); +} + ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) { if (QThread::currentThread() != thread()) { ScriptAudioInjector* injector = NULL; diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 9ee2af7738..23a0861acd 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -30,8 +30,10 @@ public: protected: AudioScriptingInterface() {} - // this method is protected to stop C++ callers from calling, but invokable from script + // these methods are protected to stop C++ callers from calling, but invokable from script Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); + // FIXME: there is no way to play a positionless sound + Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position); Q_INVOKABLE void setStereoInput(bool stereo); diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.cpp b/libraries/script-engine/src/ConsoleScriptingInterface.cpp new file mode 100644 index 0000000000..f3ceee63f7 --- /dev/null +++ b/libraries/script-engine/src/ConsoleScriptingInterface.cpp @@ -0,0 +1,182 @@ +// +// ConsoleScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by NeetBhagat on 6/1/17. +// Copyright 2014 High Fidelity, Inc. +// +// ConsoleScriptingInterface is responsible for following functionality +// Printing logs with various tags and grouping on debug Window and Logs/log file. +// Debugging functionalities like Timer start-end, assertion, trace. +// To use these functionalities, use "console" object in Javascript files. +// For examples please refer "scripts/developer/tests/consoleObjectTest.js" +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ConsoleScriptingInterface.h" +#include "ScriptEngine.h" + +#define INDENTATION 4 // 1 Tab - 4 spaces +const QString LINE_SEPARATOR = "\n "; +const QString SPACE_SEPARATOR = " "; +const QString STACK_TRACE_FORMAT = "\n[Stacktrace]%1%2"; +QList ConsoleScriptingInterface::_groupDetails = QList(); + +QScriptValue ConsoleScriptingInterface::info(QScriptContext* context, QScriptEngine* engine) { + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptInfoMessage(appendArguments(context)); + } + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::log(QScriptContext* context, QScriptEngine* engine) { + QString message = appendArguments(context); + if (_groupDetails.count() == 0) { + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptPrintedMessage(message); + } + } else { + logGroupMessage(message, engine); + } + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::debug(QScriptContext* context, QScriptEngine* engine) { + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptPrintedMessage(appendArguments(context)); + } + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::warn(QScriptContext* context, QScriptEngine* engine) { + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptWarningMessage(appendArguments(context)); + } + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::error(QScriptContext* context, QScriptEngine* engine) { + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptErrorMessage(appendArguments(context)); + } + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::exception(QScriptContext* context, QScriptEngine* engine) { + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptErrorMessage(appendArguments(context)); + } + return QScriptValue::NullValue; +} + +void ConsoleScriptingInterface::time(QString labelName) { + _timerDetails.insert(labelName, QDateTime::currentDateTime().toUTC()); + QString message = QString("%1: Timer started").arg(labelName); + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->scriptPrintedMessage(message); + } +} + +void ConsoleScriptingInterface::timeEnd(QString labelName) { + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + if (!_timerDetails.contains(labelName)) { + scriptEngine->scriptErrorMessage("No such label found " + labelName); + return; + } + + if (_timerDetails.value(labelName).isNull()) { + _timerDetails.remove(labelName); + scriptEngine->scriptErrorMessage("Invalid start time for " + labelName); + return; + } + QDateTime _startTime = _timerDetails.value(labelName); + QDateTime _endTime = QDateTime::currentDateTime().toUTC(); + qint64 diffInMS = _startTime.msecsTo(_endTime); + + QString message = QString("%1: %2ms").arg(labelName).arg(QString::number(diffInMS)); + _timerDetails.remove(labelName); + + scriptEngine->scriptPrintedMessage(message); + } +} + +QScriptValue ConsoleScriptingInterface::assertion(QScriptContext* context, QScriptEngine* engine) { + QString message; + bool condition = false; + for (int i = 0; i < context->argumentCount(); i++) { + if (i == 0) { + condition = context->argument(i).toBool(); // accept first value as condition + } else { + message += SPACE_SEPARATOR + context->argument(i).toString(); // accept other parameters as message + } + } + + QString assertionResult; + if (!condition) { + if (message.isEmpty()) { + assertionResult = "Assertion failed"; + } else { + assertionResult = QString("Assertion failed : %1").arg(message); + } + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptErrorMessage(assertionResult); + } + } + return QScriptValue::NullValue; +} + +void ConsoleScriptingInterface::trace() { + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->scriptPrintedMessage + (QString(STACK_TRACE_FORMAT).arg(LINE_SEPARATOR, + scriptEngine->currentContext()->backtrace().join(LINE_SEPARATOR))); + } +} + +void ConsoleScriptingInterface::clear() { + if (ScriptEngine* scriptEngine = qobject_cast(engine())) { + scriptEngine->clearDebugLogWindow(); + } +} + +QScriptValue ConsoleScriptingInterface::group(QScriptContext* context, QScriptEngine* engine) { + logGroupMessage(context->argument(0).toString(), engine); // accept first parameter as label + _groupDetails.push_back(context->argument(0).toString()); + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::groupCollapsed(QScriptContext* context, QScriptEngine* engine) { + logGroupMessage(context->argument(0).toString(), engine); // accept first parameter as label + _groupDetails.push_back(context->argument(0).toString()); + return QScriptValue::NullValue; +} + +QScriptValue ConsoleScriptingInterface::groupEnd(QScriptContext* context, QScriptEngine* engine) { + ConsoleScriptingInterface::_groupDetails.removeLast(); + return QScriptValue::NullValue; +} + +QString ConsoleScriptingInterface::appendArguments(QScriptContext* context) { + QString message; + for (int i = 0; i < context->argumentCount(); i++) { + if (i > 0) { + message += SPACE_SEPARATOR; + } + message += context->argument(i).toString(); + } + return message; +} + +void ConsoleScriptingInterface::logGroupMessage(QString message, QScriptEngine* engine) { + int _addSpaces = _groupDetails.count() * INDENTATION; + QString logMessage; + for (int i = 0; i < _addSpaces; i++) { + logMessage.append(SPACE_SEPARATOR); + } + logMessage.append(message); + if (ScriptEngine* scriptEngine = qobject_cast(engine)) { + scriptEngine->scriptPrintedMessage(logMessage); + } +} diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.h b/libraries/script-engine/src/ConsoleScriptingInterface.h new file mode 100644 index 0000000000..444ea93504 --- /dev/null +++ b/libraries/script-engine/src/ConsoleScriptingInterface.h @@ -0,0 +1,56 @@ +// +// ConsoleScriptingInterface.h +// libraries/script-engine/src +// +// Created by NeetBhagat on 6/1/17. +// Copyright 2014 High Fidelity, Inc. +// +// ConsoleScriptingInterface is responsible for following functionality +// Printing logs with various tags and grouping on debug Window and Logs/log file. +// Debugging functionalities like Timer start-end, assertion, trace. +// To use these functionalities, use "console" object in Javascript files. +// For examples please refer "scripts/developer/tests/consoleObjectTest.js" +// +// 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 +#ifndef hifi_ConsoleScriptingInterface_h +#define hifi_ConsoleScriptingInterface_h + +#include +#include +#include +#include +#include + +// Scriptable interface of "console" object. Used exclusively in the JavaScript API +class ConsoleScriptingInterface : public QObject, protected QScriptable { + Q_OBJECT +public: + static QScriptValue info(QScriptContext* context, QScriptEngine* engine); + static QScriptValue log(QScriptContext* context, QScriptEngine* engine); + static QScriptValue debug(QScriptContext* context, QScriptEngine* engine); + static QScriptValue warn(QScriptContext* context, QScriptEngine* engine); + static QScriptValue error(QScriptContext* context, QScriptEngine* engine); + static QScriptValue exception(QScriptContext* context, QScriptEngine* engine); + static QScriptValue assertion(QScriptContext* context, QScriptEngine* engine); + static QScriptValue group(QScriptContext* context, QScriptEngine* engine); + static QScriptValue groupCollapsed(QScriptContext* context, QScriptEngine* engine); + static QScriptValue groupEnd(QScriptContext* context, QScriptEngine* engine); + +public slots: + void time(QString labelName); + void timeEnd(QString labelName); + void trace(); + void clear(); + +private: + QHash _timerDetails; + static QList _groupDetails; + static void logGroupMessage(QString message, QScriptEngine* engine); + static QString appendArguments(QScriptContext* context); +}; + +#endif // hifi_ConsoleScriptingInterface_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 9763f0d715..5c4c2c849b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -472,20 +472,24 @@ void ScriptEngine::scriptErrorMessage(const QString& message) { } void ScriptEngine::scriptWarningMessage(const QString& message) { - qCWarning(scriptengine) << message; + qCWarning(scriptengine) << qPrintable(message); emit warningMessage(message, getFilename()); } void ScriptEngine::scriptInfoMessage(const QString& message) { - qCInfo(scriptengine) << message; + qCInfo(scriptengine) << qPrintable(message); emit infoMessage(message, getFilename()); } void ScriptEngine::scriptPrintedMessage(const QString& message) { - qCDebug(scriptengine) << message; + qCDebug(scriptengine) << qPrintable(message); emit printedMessage(message, getFilename()); } +void ScriptEngine::clearDebugLogWindow() { + emit clearDebugWindow(); +} + // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of // callAnimationStateHandler requires that the type be registered. // These two are meaningful, if we ever do want to use them... @@ -668,8 +672,18 @@ void ScriptEngine::init() { registerGlobalObject("Mat4", &_mat4Library); registerGlobalObject("Uuid", &_uuidLibrary); registerGlobalObject("Messages", DependencyManager::get().data()); - registerGlobalObject("File", new FileScriptingInterface(this)); + registerGlobalObject("console", &_consoleScriptingInterface); + registerFunction("console", "info", ConsoleScriptingInterface::info, currentContext()->argumentCount()); + registerFunction("console", "log", ConsoleScriptingInterface::log, currentContext()->argumentCount()); + registerFunction("console", "debug", ConsoleScriptingInterface::debug, currentContext()->argumentCount()); + registerFunction("console", "warn", ConsoleScriptingInterface::warn, currentContext()->argumentCount()); + registerFunction("console", "error", ConsoleScriptingInterface::error, currentContext()->argumentCount()); + registerFunction("console", "exception", ConsoleScriptingInterface::exception, currentContext()->argumentCount()); + registerFunction("console", "assert", ConsoleScriptingInterface::assertion, currentContext()->argumentCount()); + registerFunction("console", "group", ConsoleScriptingInterface::group, 1); + registerFunction("console", "groupCollapsed", ConsoleScriptingInterface::groupCollapsed, 1); + registerFunction("console", "groupEnd", ConsoleScriptingInterface::groupEnd, 0); qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 010cdfbc75..9da8603814 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -41,6 +41,7 @@ #include "ScriptCache.h" #include "ScriptUUID.h" #include "Vec3.h" +#include "ConsoleScriptingInterface.h" #include "SettingHandle.h" class QScriptEngineDebugger; @@ -225,7 +226,7 @@ public: void scriptWarningMessage(const QString& message); void scriptInfoMessage(const QString& message); void scriptPrintedMessage(const QString& message); - + void clearDebugLogWindow(); int getNumRunningEntityScripts() const; bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; @@ -245,6 +246,7 @@ signals: void warningMessage(const QString& message, const QString& scriptName); void infoMessage(const QString& message, const QString& scriptName); void runningStateChanged(); + void clearDebugWindow(); void loadScript(const QString& scriptName, bool isUserLoaded); void reloadScript(const QString& scriptName, bool isUserLoaded); void doneRunning(); @@ -305,6 +307,7 @@ protected: Vec3 _vec3Library; Mat4 _mat4Library; ScriptUUID _uuidLibrary; + ConsoleScriptingInterface _consoleScriptingInterface; std::atomic _isUserLoaded { false }; bool _isReloading { false }; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index b310e137b7..69de067d10 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -54,6 +54,10 @@ void ScriptEngines::onInfoMessage(const QString& message, const QString& scriptN emit infoMessage(message, scriptName); } +void ScriptEngines::onClearDebugWindow() { + emit clearDebugWindow(); +} + void ScriptEngines::onErrorLoadingScript(const QString& url) { emit errorLoadingScript(url); } diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 5152c3952a..91dc54a0ec 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -79,6 +79,7 @@ signals: void warningMessage(const QString& message, const QString& engineName); void infoMessage(const QString& message, const QString& engineName); void errorLoadingScript(const QString& url); + void clearDebugWindow(); public slots: void onPrintedMessage(const QString& message, const QString& scriptName); @@ -86,6 +87,7 @@ public slots: void onWarningMessage(const QString& message, const QString& scriptName); void onInfoMessage(const QString& message, const QString& scriptName); void onErrorLoadingScript(const QString& url); + void onClearDebugWindow(); protected slots: void onScriptFinished(const QString& fileNameString, ScriptEngine* engine); diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index b41c548e57..ef2887fdfb 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -614,15 +614,6 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) { } } -void TabletProxy::updateAudioBar(const double micLevel) { - auto tablet = getQmlTablet(); - if (!tablet) { - //qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; - } else { - QMetaObject::invokeMethod(tablet, "setMicLevel", Qt::AutoConnection, Q_ARG(QVariant, QVariant(micLevel))); - } -} - void TabletProxy::emitScriptEvent(QVariant msg) { if (!_toolbarMode && _qmlOffscreenSurface) { QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 2ae9e70846..23d7ecaea4 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -152,13 +152,6 @@ public: */ Q_INVOKABLE void removeButton(QObject* tabletButtonProxy); - /**jsdoc - * Updates the audio bar in tablet to reflect latest mic level - * @function TabletProxy#updateAudioBar - * @param micLevel {double} mic level value between 0 and 1 - */ - Q_INVOKABLE void updateAudioBar(const double micLevel); - /**jsdoc * Used to send an event to the html/js embedded in the tablet * @function TabletProxy#emitScriptEvent diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 58d39448ac..f5bb880957 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -153,6 +153,9 @@ void QmlWindowClass::sendToQml(const QVariant& message) { QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); } +void QmlWindowClass::clearDebugWindow() { + QMetaObject::invokeMethod(asQuickItem(), "clearDebugWindow", Qt::QueuedConnection); +} void QmlWindowClass::emitScriptEvent(const QVariant& scriptMessage) { if (QThread::currentThread() != thread()) { diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 95777718bf..4f604133a5 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -53,6 +53,7 @@ public slots: // Scripts can use this to send a message to the QML object void sendToQml(const QVariant& message); + void clearDebugWindow(); // QmlWindow content may include WebView requiring EventBridge. void emitScriptEvent(const QVariant& scriptMessage); diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 30a050e667..6dd116089a 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -49,4 +49,8 @@ ScriptDiscoveryService.infoMessage.connect(function(message, scriptFileName) { sendToLogWindow("INFO", message, scriptFileName); }); -}()); // END LOCAL_SCOPE \ No newline at end of file +ScriptDiscoveryService.clearDebugWindow.connect(function() { + window.clearDebugWindow(); +}); + +}()); diff --git a/scripts/developer/debugging/debugWindow.qml b/scripts/developer/debugging/debugWindow.qml index 20fa24358d..2370803335 100644 --- a/scripts/developer/debugging/debugWindow.qml +++ b/scripts/developer/debugging/debugWindow.qml @@ -40,6 +40,10 @@ Rectangle { } textArea.append(message); } + + function clearWindow() { + textArea.remove(0,textArea.length); + } } diff --git a/scripts/developer/tests/consoleObjectTest.js b/scripts/developer/tests/consoleObjectTest.js new file mode 100644 index 0000000000..a59652dc09 --- /dev/null +++ b/scripts/developer/tests/consoleObjectTest.js @@ -0,0 +1,114 @@ +// Examples and understanding of console object. Include console methods like +// info, log, debug, warn, error, exception, trace, clear, asserts, group, groupCollapsed, groupEnd, time, timeEnd. +// Useful in debugging and exclusively made for JavaScript files. +// To view the logs click on Developer -> script logs [logs on debug window] and for text file logs go to Logs/log file. + +main(); + +function main() { + + var someObject = { str: "Some text", id: 5 }; + var someValue = 5; + + // console.info examples + console.info("[console.info] Hello World."); + console.info(5 + 6); + console.info(someObject.str); + console.info(a = 2 * 6); + console.info(someValue); + console.info('someObject id ' + someObject.id); + console.info('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6); + + // console.log examples + console.log("[console.log] Hello World"); + console.log(5 + 6); + console.log(someObject.str); + console.log(a = 2 * 6); + console.log(someValue); + console.log('someObject id ' + someObject.id); + console.log('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6); + + // console.debug examples + console.debug("[console.debug] Hello World."); + console.debug(5 + 6); + console.debug(someObject.str); + console.debug(a = 2 * 6); + console.debug(someValue); + console.debug('someObject id ' + someObject.id); + console.debug('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6); + + // console.warn examples + console.warn("[console.warn] This is warning message."); + console.warn(5 + 6); + console.warn(someObject.str); + console.warn(a = 2 * 6); + console.warn(someValue); + console.warn('someObject id ' + someObject.id); + console.warn('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6); + + // console.error examples + console.error('An error occurred!'); + console.error('An error occurred! ', 'Value = ', someValue); + console.error('An error occurred! ' + 'Value = ' + someValue); + + // console.exception examples + console.exception('An exception occurred!'); + console.exception('An exception occurred! ', 'Value = ', someValue); + console.exception('An exception occurred! ' + 'Value = ' + someValue); + + // console.trace examples + function fooA() { + function fooB() { + function fooC() { + console.trace(); + } + fooC(); + } + fooB(); + } + fooA(); + + // console.assert() examples + var valA = 1, valB = "1"; + console.assert(valA === valB, "Value A doesn't equal to B"); + console.assert(valA === valB); + console.assert(5 === 5, "5 equals to 5"); + console.assert(5 === 5); + console.assert(5 > 6, "5 is not greater than 6"); + console.assert(5 > 6, "5 is not greater than 6", "This assertion will fail"); + + // console.group() examples. + console.group("Group 1"); + console.log("Sentence 1"); + console.log("Sentence 2"); + console.group("Group 2"); + console.log("Sentence 3"); + console.log("Sentence 4"); + console.groupCollapsed("Group 3"); + console.log("Sentence 5"); + console.log("Sentence 6"); + console.groupEnd(); + console.log("Sentence 7"); + console.groupEnd(); + console.log("Sentence 8"); + console.groupEnd(); + console.log("Sentence 9"); + + // console.time(),console.timeEnd() examples + console.time('MyTimer'); + // Do some process + sleep(1000); + console.timeEnd('MyTimer'); + + // use console.clear() to clean Debug Window logs + // console.clear(); +} + +function sleep(milliseconds) { + var start = new Date().getTime(); + for (var i = 0; i < 1e7; i++) { + if ((new Date().getTime() - start) > milliseconds){ + break; + } + } +} diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a3583e7808..a83d2159bb 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -622,7 +622,7 @@ var toolBar = (function () { })); isActive = active; activeButton.editProperties({isActive: isActive}); - + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); if (!isActive) { @@ -1519,6 +1519,8 @@ function importSVO(importURL) { // entities after they're imported so that they're all the correct distance in front of and with geometric mean // centered on the avatar/camera direction. var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]); var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; @@ -1534,10 +1536,9 @@ function importSVO(importURL) { var targetPosition = getPositionToCreateEntity(); var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. - var entityPositions = []; for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", - "registrationPoint", "rotation"]); + "registrationPoint", "rotation", "parentID"]); var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, properties.registrationPoint, properties.dimensions, properties.rotation); var delta = Vec3.subtract(adjustedPosition, properties.position); @@ -1546,6 +1547,7 @@ function importSVO(importURL) { deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), deltaPerpendicular); entityPositions[i] = properties.position; + entityParentIDs[i] = properties.parentID; } deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); @@ -1562,9 +1564,11 @@ function importSVO(importURL) { if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { - Entities.editEntity(pastedEntityIDs[i], { - position: Vec3.sum(deltaPosition, entityPositions[i]) - }); + if (Uuid.isNull(entityParentIDs[i])) { + Entities.editEntity(pastedEntityIDs[i], { + position: Vec3.sum(deltaPosition, entityPositions[i]) + }); + } } } } diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index b7324ed28c..9188f39a2e 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -183,11 +183,6 @@ return; } - //TODO: move to tablet qml? - if (tabletShown) { - gTablet.updateAudioBar(getMicLevel()); - } - if (now - validCheckTime > MSECS_PER_SEC) { validCheckTime = now; updateTabletWidthFromSettings(); @@ -268,12 +263,6 @@ Script.setInterval(updateShowTablet, 100); - // Calculate microphone level with the same scaling equation (log scale, exponentially averaged) in AvatarInputs and pal.js - function getMicLevel() { - //reuse already existing C++ code - return AvatarInputs.loudnessToAudioLevel(MyAvatar.audioLoudness) - } - Script.scriptEnding.connect(function () { // if we reload scripts in tablet mode make sure we close the currently open window, by calling gotoHomeScreen