364 lines
11 KiB
QML
364 lines
11 KiB
QML
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
import QtQuick 2.12
|
|
import QtQuick.Window 2.12
|
|
import QtQuick.Layouts 1.12
|
|
import Clipboard 0.1
|
|
import "Base"
|
|
import "ShortcutBundles"
|
|
|
|
HDrawer {
|
|
id: debugConsole
|
|
|
|
property Item previouslyFocused: null
|
|
|
|
property QtObject target: null
|
|
property alias t: debugConsole.target
|
|
|
|
property var history: window.history.console
|
|
property int historyEntry: -1
|
|
property int maxHistoryLength: 4096
|
|
property var textBeforeHistoryNavigation: null // null or string
|
|
|
|
property int selectedOutputDelegateIndex: -1
|
|
property string selectedOutputText: ""
|
|
|
|
property string pythonDebugKeybind:
|
|
window.settings.keys.startPythonDebugger[0]
|
|
|
|
property string help: qsTr(
|
|
`Interact with the QML code using JavaScript ES6 syntax.
|
|
|
|
Useful variables:
|
|
t Target item to debug for which this console was opened
|
|
this The console itself
|
|
py Python interpreter (${pythonDebugKeybind} to start debugger)
|
|
|
|
window, mainUI, theme, settings, utils, mainPane, pageLoader
|
|
|
|
Special commands:
|
|
.j OBJECT, .json OBJECT Print OBJECT as human-readable JSON
|
|
|
|
.t, .top Attach console to the parent window's top
|
|
.b, .bottom Attach console to the parent window's bottom
|
|
.l, .left Attach console to the parent window's left
|
|
.r, .right Attach console to the parent window's right
|
|
.h, .help Show this help`.replace(/^ {8}/gm, "")
|
|
)
|
|
|
|
property bool doUselessThing: false
|
|
property real baseGIFSpeed: 1.0
|
|
|
|
readonly property alias outputList: outputList
|
|
|
|
function toggle(targetItem=null, js="", addToHistory=false) {
|
|
if (debugConsole.visible) {
|
|
debugConsole.visible = false
|
|
return
|
|
}
|
|
|
|
debugConsole.visible = true
|
|
debugConsole.target =
|
|
! targetItem && ! debugConsole.target ? mainUI :
|
|
targetItem ? targetItem :
|
|
debugConsole.target
|
|
|
|
if (js) debugConsole.runJS(js, addToHistory)
|
|
}
|
|
|
|
function runJS(input, addToHistory=true) {
|
|
if (addToHistory && history.slice(-1)[0] !== input) {
|
|
history.push(input)
|
|
while (history.length > maxHistoryLength) history.shift()
|
|
window.historyChanged()
|
|
}
|
|
|
|
let output = ""
|
|
let error = false
|
|
|
|
try {
|
|
if ([".h", ".help"].includes(input)) {
|
|
output = debugConsole.help
|
|
|
|
} else if ([".t", ".top"].includes(input)) {
|
|
debugConsole.edge = Qt.TopEdge
|
|
|
|
} else if ([".b", ".bottom"].includes(input)) {
|
|
debugConsole.edge = Qt.BottomEdge
|
|
|
|
} else if ([".l", ".left"].includes(input)) {
|
|
debugConsole.edge = Qt.LeftEdge
|
|
|
|
} else if ([".r", ".right"].includes(input)) {
|
|
debugConsole.edge = Qt.RightEdge
|
|
|
|
} else if (input.startsWith(".j ") || input.startsWith(".json ")) {
|
|
output = JSON.stringify(eval(input.substring(2)), null, 4)
|
|
|
|
} else {
|
|
let result = eval(input)
|
|
output = result instanceof Array ?
|
|
"[" + String(result) + "]" : String(result)
|
|
}
|
|
|
|
} catch (err) {
|
|
error = true
|
|
output = err.toString()
|
|
}
|
|
|
|
outputList.model.insert(0, { input, output, error })
|
|
}
|
|
|
|
|
|
objectName: "debugConsole"
|
|
edge: Qt.TopEdge
|
|
x: horizontal ? 0 : referenceSizeParent.width / 2 - width / 2
|
|
y: vertical ? 0 : referenceSizeParent.height / 2 - height / 2
|
|
width: horizontal ? calculatedSize : Math.min(window.width, 720)
|
|
height: vertical ? calculatedSize : Math.min(window.height, 720)
|
|
defaultSize: 400
|
|
z: 9999
|
|
position: 0
|
|
|
|
onTargetChanged: {
|
|
outputList.model.insert(0, {
|
|
input: "t = " + String(target),
|
|
output: "",
|
|
error: false,
|
|
})
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if (visible) {
|
|
previouslyFocused = window.activeFocusItem
|
|
forceActiveFocus()
|
|
} else if (previouslyFocused) {
|
|
previouslyFocused.forceActiveFocus()
|
|
}
|
|
}
|
|
|
|
onHistoryEntryChanged: {
|
|
if (historyEntry === -1) {
|
|
inputArea.clear()
|
|
inputArea.append(textBeforeHistoryNavigation)
|
|
textBeforeHistoryNavigation = null
|
|
return
|
|
}
|
|
|
|
if (textBeforeHistoryNavigation === null)
|
|
textBeforeHistoryNavigation = inputArea.text
|
|
|
|
inputArea.clear()
|
|
inputArea.append(history.slice(-historyEntry - 1)[0])
|
|
}
|
|
|
|
HShortcut {
|
|
sequences: settings.keys.toggleDebugConsole
|
|
onActivated: debugConsole.toggle()
|
|
}
|
|
|
|
HColumnLayout {
|
|
anchors.fill: parent
|
|
// Fixes inputArea cursor invisible when at cursorPosition 0
|
|
anchors.leftMargin: 1
|
|
|
|
HListView {
|
|
id: outputList
|
|
spacing: theme.spacing
|
|
topMargin: theme.spacing
|
|
bottomMargin: topMargin
|
|
leftMargin: theme.spacing
|
|
rightMargin: leftMargin
|
|
clip: true
|
|
verticalLayoutDirection: ListView.BottomToTop
|
|
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
|
|
model: ListModel {}
|
|
|
|
delegate: HSelectableLabel {
|
|
id: delegate
|
|
width: outputList.width -
|
|
outputList.leftMargin - outputList.rightMargin
|
|
|
|
readonly property color inputColor:
|
|
model.error ? theme.colors.errorText :
|
|
model.output ? theme.colors.accentText :
|
|
theme.colors.positiveText
|
|
|
|
text:
|
|
`<div style="white-space: pre-wrap">` +
|
|
`<font color="${inputColor}">` +
|
|
utils.plain2Html(model.input) +
|
|
"</font>" +
|
|
(model.input && model.output ? "<br>" : "") +
|
|
(model.output ? utils.plain2Html(model.output) : "") +
|
|
"</div>"
|
|
|
|
leftPadding: theme.spacing
|
|
textFormat: HSelectableLabel.RichText
|
|
wrapMode: HLabel.Wrap
|
|
font.family: theme.fontFamily.mono
|
|
color:
|
|
model.error ?
|
|
Qt.darker(inputColor, 1.4) :
|
|
theme.colors.halfDimText
|
|
|
|
Layout.fillWidth: true
|
|
|
|
onSelectedTextChanged: if (selectedText) {
|
|
selectedOutputDelegateIndex = model.index
|
|
selectedOutputText = selectedText
|
|
} else if (selectedOutputDelegateIndex === model.index) {
|
|
selectedOutputDelegateIndex = -1
|
|
selectedOutputText = ""
|
|
}
|
|
|
|
Connections {
|
|
target: debugConsole
|
|
onSelectedOutputDelegateIndexChanged: {
|
|
if (selectedOutputDelegateIndex !== model.index)
|
|
delegate.deselect()
|
|
}
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.RightButton
|
|
gesturePolicy: TapHandler.ReleaseWithinBounds
|
|
acceptedPointerTypes:
|
|
PointerDevice.GenericPointer | PointerDevice.Pen
|
|
|
|
onTapped: menu.popup()
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedPointerTypes:
|
|
PointerDevice.Finger | PointerDevice.Pen
|
|
|
|
onLongPressed: menu.popup()
|
|
}
|
|
|
|
HMenu {
|
|
id: menu
|
|
implicitWidth: Math.min(window.width, 150)
|
|
z: 10000
|
|
|
|
HMenuItem {
|
|
icon.name: "copy-text"
|
|
text: qsTr("Copy")
|
|
onTriggered: {
|
|
if (delegate.selectedText) {
|
|
delegate.copy()
|
|
return
|
|
}
|
|
delegate.selectAll()
|
|
delegate.copy()
|
|
delegate.deselect()
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: 1
|
|
height: parent.height
|
|
color:
|
|
model.error ?
|
|
theme.colors.negativeBackground :
|
|
model.output ?
|
|
theme.colors.accentElement :
|
|
theme.colors.positiveBackground
|
|
}
|
|
}
|
|
|
|
FlickShortcuts {
|
|
active: debugConsole.visible
|
|
flickable: outputList
|
|
}
|
|
|
|
Rectangle {
|
|
z: -10
|
|
anchors.fill: parent
|
|
color: theme.colors.weakBackground
|
|
}
|
|
}
|
|
|
|
HTextArea {
|
|
id: inputArea
|
|
|
|
readonly property int cursorY:
|
|
text.substring(0, cursorPosition).split("\n").length - 1
|
|
|
|
function accept() {
|
|
if (! text) return
|
|
runJS(text)
|
|
clear()
|
|
historyEntry = -1
|
|
}
|
|
|
|
focus: true
|
|
backgroundColor: Qt.hsla(0, 0, 0, 0.85)
|
|
bordered: false
|
|
placeholderText: qsTr("QML/JavaScript debug console - Type .help")
|
|
font.family: theme.fontFamily.mono
|
|
|
|
Keys.onUpPressed: ev => {
|
|
ev.accepted =
|
|
cursorY === 0 && historyEntry + 1 < history.length
|
|
|
|
if (ev.accepted) {
|
|
historyEntry += 1
|
|
cursorPosition = length
|
|
}
|
|
}
|
|
|
|
Keys.onDownPressed: ev => {
|
|
ev.accepted =
|
|
cursorY === lineCount - 1 && historyEntry - 1 >= -1
|
|
|
|
if (ev.accepted) historyEntry -= 1
|
|
}
|
|
|
|
Keys.onTabPressed: inputArea.insertAtCursor(" ")
|
|
|
|
Keys.onReturnPressed: ev => {
|
|
ev.modifiers & Qt.ShiftModifier ||
|
|
ev.modifiers & Qt.ControlModifier ||
|
|
ev.modifiers & Qt.AltModifier ?
|
|
inputArea.insertAtCursor("\n") :
|
|
accept()
|
|
}
|
|
|
|
Keys.onEnterPressed: ev => Keys.returnPressed(ev)
|
|
|
|
Keys.onEscapePressed: debugConsole.close()
|
|
|
|
Keys.onPressed: ev => {
|
|
if (
|
|
ev.matches(StandardKey.Copy) &&
|
|
! inputArea.selectedText &&
|
|
selectedOutputText
|
|
) {
|
|
ev.accepted = true
|
|
Clipboard.text = selectedOutputText
|
|
}
|
|
}
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
}
|
|
|
|
NumberAnimation {
|
|
running: doUselessThing
|
|
target: mainUI.mainPane.roomList
|
|
property: "rotation"
|
|
duration: 250
|
|
from: 360
|
|
to: 0
|
|
loops: Animation.Infinite
|
|
onStopped: target.rotation = 0
|
|
}
|
|
}
|