diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js
index c0474b97c5c..6c5e0206371 100644
--- a/scripts/developer/debugging/debugWindow.js
+++ b/scripts/developer/debugging/debugWindow.js
@@ -9,20 +9,20 @@
//
(function() { // BEGIN LOCAL_SCOPE
-
+"use strict"
//check if script already running.
-var scriptData = ScriptDiscoveryService.getRunning();
-var scripts = scriptData.filter(function (datum) { return datum.name === 'debugWindow.js'; });
+const scriptData = ScriptDiscoveryService.getRunning();
+const scripts = scriptData.filter(function (datum) { return datum.name === 'debugWindow.js'; });
if (scripts.length >= 2) {
//2nd instance of the script is too much
ScriptDiscoveryService.stopScript(scripts[1].url);
return;
}
-var SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME = "Developer"
-var SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME = "Suppress messages from default scripts in Debug Window";
-var DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS = 'debugWindowSuppressDefaultScripts';
-var suppressDefaultScripts = Settings.getValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, false)
+const SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME = "Developer"
+const SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME = "Suppress messages from default scripts in Debug Window";
+const DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS = 'debugWindowSuppressDefaultScripts';
+let suppressDefaultScripts = Settings.getValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, false)
Menu.addMenuItem({
menuName: SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME,
menuItemName: SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME,
@@ -37,30 +37,36 @@ Menu.menuItemEvent.connect(function(menuItem) {
});
// Set up the qml ui
-var qml = Script.resolvePath('debugWindow.qml');
+const qml = Script.resolvePath('debugWindow.qml');
-var HMD_DEBUG_WINDOW_GEOMETRY_KEY = 'hmdDebugWindowGeometry';
-var hmdDebugWindowGeometryValue = Settings.getValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY);
+const HMD_DEBUG_WINDOW_GEOMETRY_KEY = 'hmdDebugWindowGeometry';
+const hmdDebugWindowGeometryValue = Settings.getValue(HMD_DEBUG_WINDOW_GEOMETRY_KEY);
-var windowWidth = 400;
-var windowHeight = 900;
+let windowWidth = 400;
+let windowHeight = 900;
-var hasPosition = false;
-var windowX = 0;
-var windowY = 0;
+let hasPosition = false;
+let windowX = 0;
+let windowY = 0;
if (hmdDebugWindowGeometryValue !== '') {
- var geometry = JSON.parse(hmdDebugWindowGeometryValue);
+ const geometry = JSON.parse(hmdDebugWindowGeometryValue);
if ((geometry.x !== 0) && (geometry.y !== 0)) {
windowWidth = geometry.width;
windowHeight = geometry.height;
windowX = geometry.x;
windowY = geometry.y;
+
+ // Constrain window position to within the viewport
+ const viewportDimensions = Controller.getViewportDimensions();
+ if (windowX > viewportDimensions.x) windowX = viewportDimensions.x - windowWidth;
+ if (windowY > viewportDimensions.y) windowX = viewportDimensions.y - windowHeight;
+
hasPosition = true;
}
}
-var window = new OverlayWindow({
+const window = new OverlayWindow({
title: 'Debug Window',
source: qml,
width: windowWidth, height: windowHeight,
@@ -92,18 +98,23 @@ function shouldLogMessage(scriptFileName) {
|| (scriptFileName !== "defaultScripts.js" && scriptFileName != "controllerScripts.js");
}
-var getFormattedDate = function() {
- var date = new Date();
+const getFormattedDate = function() {
+ const date = new Date();
return date.getMonth() + "/" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
};
-var sendToLogWindow = function(type, message, scriptFileName) {
+const sendToLogWindow = function(type, message, scriptFileName) {
if (shouldLogMessage(scriptFileName)) {
- var typeFormatted = "";
+ let typeFormatted = "";
if (type) {
typeFormatted = type + " - ";
}
- window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message);
+ window.sendToQml({
+ date: getFormattedDate(),
+ scriptFileName: scriptFileName,
+ type: type,
+ message: message,
+ });
}
};
@@ -132,7 +143,7 @@ Script.scriptEnding.connect(function () {
Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME));
Menu.removeMenuItem(SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME);
- var geometry = JSON.stringify({
+ const geometry = JSON.stringify({
x: window.position.x,
y: window.position.y,
width: window.size.x,
diff --git a/scripts/developer/debugging/debugWindow.qml b/scripts/developer/debugging/debugWindow.qml
index 23708033351..3619d3078e8 100644
--- a/scripts/developer/debugging/debugWindow.qml
+++ b/scripts/developer/debugging/debugWindow.qml
@@ -8,13 +8,18 @@
// 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 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.0
import Hifi 1.0 as Hifi
+import TabletScriptingInterface 1.0
+import stylesUit 1.0
-Rectangle {
+
+ColumnLayout {
id: root
+ HifiConstants { id: hifi }
width: parent ? parent.width : 100
height: parent ? parent.height : 100
@@ -23,22 +28,246 @@ Rectangle {
property var channel;
- TextArea {
- id: textArea
- width: parent.width
- height: parent.height
- text:""
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.preferredWidth: parent.width
+ color: hifi.colors.darkGray
+
+ ScrollView {
+ id: logView
+ anchors.fill: parent
+ clip: true
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ ScrollBar.vertical.policy: ScrollBar.AlwaysOn
+
+ TextArea {
+ id: textArea
+ width: root.width
+ height: parent.height - bottomRow.height
+ wrapMode: TextArea.Wrap
+ placeholderText: qsTr("Debug messages from your running scripts will appear here...")
+ placeholderTextColor: hifi.colors.lightGrayText
+ readOnly: true
+ selectByMouse: true
+ textFormat: TextEdit.RichText
+
+ background: Rectangle {
+ color: "transparent"
+ }
+ text:""
+
+ onTextChanged: {
+ if (logView.scrollDelay) {
+ // Scroll to the bottom, but only once within a short period of time
+ // rather than for every message.
+ scrollTimer.restart();
+ } else {
+ // Scroll to the bottom, immediately!
+ logView.scrollToBottom();
+ logView.scrollDelay = true;
+ }
+ }
+ }
+
+ // Do we want to use the timer for the next scroll (true),
+ // or scroll immediately (false);
+ property bool scrollDelay: true
+
+ Timer {
+ id: scrollTimer
+ interval: 1000; running: false; repeat: false;
+ onTriggered: {
+ logView.scrollToBottom();
+ }
+ }
+
+ function scrollToBottom() { // This is not strictly necessary now, but is kept as a fail-safe
+ logView.ScrollBar.vertical.position = 1.0 - logView.ScrollBar.vertical.size;
+ }
+ }
+
+ }
+
+ RowLayout {
+ id: bottomRow
+ Layout.preferredWidth: parent.width
+
+ // Helpful text
+ Text {
+ Layout.fillWidth: true
+ Layout.preferredWidth: parent.width
+ Layout.maximumHeight: selectScriptName.height
+ text: "Clear view and filter logs by script name"
+ color: hifi.colors.lightGrayText
+ horizontalAlignment: Text.AlignRight
+ wrapMode: Text.WordWrap
+ clip: true
+
+
+ }
+
+ Text {
+ text: "-->"
+ color: hifi.colors.lightGrayText
+ horizontalAlignment: Text.AlignHCenter
+ clip:true
+ }
+
+ // Drop down menu with all seen script names
+ ComboBox {
+ id: selectScriptName
+ Layout.preferredWidth: 150
+ Layout.preferredHeight: 35;
+ padding: 0
+ model: scriptNames
+
+ background: Rectangle {
+ id: comboBoxBackground;
+ color: "#333";
+ radius: 10;
+ }
+
+ contentItem: Text {
+ anchors.centerIn: parent;
+ width: parent.width;
+ text: selectScriptName.displayText;
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ elide: Text.ElideRight;
+ color: "white";
+ }
+
+ popup: Popup {
+ width: selectScriptName.width
+ padding: 1
+
+ contentItem: ListView {
+ clip: true
+ implicitHeight: contentHeight
+ model: selectScriptName.popup.visible ? selectScriptName.delegateModel : null
+ currentIndex: selectScriptName.highlightedIndex
+
+ ScrollIndicator.vertical: ScrollIndicator { }
+ }
+
+ background: Rectangle {
+ color: Qt.rgba(0,0,0,0.9)
+ radius: 10
+ }
+
+ onVisibleChanged: {
+ Tablet.playSound(TabletEnums.ButtonClicked);
+ }
+ }
+
+ indicator: Canvas {
+ id: canvas
+ x: selectScriptName.width - width - selectScriptName.rightPadding
+ y: selectScriptName.topPadding + (selectScriptName.availableHeight - height) / 2
+ width: 12
+ height: 8
+ contextType: "2d"
+
+ Connections {
+ target: selectScriptName
+ function onPressedChanged() { canvas.requestPaint(); }
+ }
+
+ onPaint: {
+ context.reset();
+ context.moveTo(0, 0);
+ context.lineTo(width, 0);
+ context.lineTo(width / 2, height);
+ context.closePath();
+ context.fillStyle = "white";
+ context.fill();
+ }
+ }
+
+ delegate: ItemDelegate {
+ contentItem: Text {
+ text: model.name
+ color: "white"
+ font: selectScriptName.font
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+ background: Rectangle {
+ color: highlighted ? "gray" : "transparent";
+ radius: 10
+ }
+ highlighted: scriptNames.highlightedIndex == index
+ }
+
+ onActivated: {
+ console.log("index: ", index);
+ console.log("currentText:", currentText);
+ if (index == 0) {
+ currentFilter = null;
+ } else {
+ currentFilter = currentText;
+ }
+ textArea.clear()
+ }
+
+ }
}
- function fromScript(message) {
- var MAX_LINE_COUNT = 2000;
- var TRIM_LINES = 500;
- if (textArea.lineCount > MAX_LINE_COUNT) {
- var lines = textArea.text.split('\n');
- lines.splice(0, TRIM_LINES);
- textArea.text = lines.join('\n');
+ ListModel {
+ id: scriptNames
+ ListElement {
+ name: "No Filter"
+ }
+ }
+
+ property var scriptNamesSet: new Set()
+
+ property var currentFilter: null;
+
+ function fromScript(line) {
+ const MAX_LINE_COUNT = 2000; // post-wrap lines
+ const TRIM_LINES = 50; // pre-wrap lines
+ if (textArea.lineCount > MAX_LINE_COUNT) { // textArea.lineCount represents the number of visual line breaks after text wrap, so is not an accurate count of the number of log lines.
+
+ let charIndex = -1;
+ for (let i = 0; i < TRIM_LINES; i++) {
+ charIndex = textArea.text.indexOf('', charIndex + 1); // This seems to result in removing ~3x the number of log lines then we requested.
+ }
+ // Here we use .remove, as editing the .text property directly results in breaking the built-in automatic scrolling when new lines are added.
+ textArea.remove(0, charIndex + 7); // + the length of
+ }
+ switch(line.type) {
+ case "WARNING":
+ line.color = hifi.colors.orangeHighlight
+ break;
+ case "ERROR":
+ line.color = hifi.colors.redHighlight
+ break;
+ case "INFO":
+ line.color = hifi.colors.indigoHighlight
+ break
+ default:
+ line.color = hifi.colors.faintGray
+ }
+
+ // Preserved formatting of text when used in html
+ const message = line.message
+ .replace(//g, ">")
+ .replace(/\n/g, "
");
+
+ if (!currentFilter || currentFilter == line.scriptFileName) {
+ // `white-space: pre-wrap` preserves white space at the start of lines
+ textArea.append(`[${line.date}] [${line.scriptFileName}] ${line.type.length > 0 ? line.type+' - ' : ""}${message}`);
+ }
+
+ if (!scriptNamesSet.has(line.scriptFileName)) {
+ const scriptNameItem = {"name": line.scriptFileName};
+ scriptNamesSet.add(line.scriptFileName);
+ scriptNames.append(scriptNameItem);
}
- textArea.append(message);
}
function clearWindow() {