364 lines
12 KiB
C#
364 lines
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
using BITKit;
|
|
using BITKit.UX;
|
|
using System.Linq;
|
|
using UnityEngine.InputSystem;
|
|
using Cysharp.Threading.Tasks;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using BITKit.Mod;
|
|
using UnityEngine.InputSystem.Interactions;
|
|
using Label = UnityEngine.UIElements.Label;
|
|
using Object = UnityEngine.Object;
|
|
|
|
// ReSharper disable PossibleMultipleEnumeration
|
|
|
|
namespace BITKit.Console
|
|
{
|
|
public class UXConsole:IDisposable
|
|
{
|
|
[BITCommand]
|
|
// ReSharper disable once UnusedMember.Global
|
|
public static void Console_Exception_Print_StackTrace(int allow)
|
|
{
|
|
_exceptionPrintStackTrace = allow is 1;
|
|
}
|
|
|
|
private readonly IMainTicker _ticker;
|
|
public UXConsole(IMainTicker ticker)
|
|
{
|
|
_ticker = ticker;
|
|
_singleton = this;
|
|
|
|
_ticker.Add(OnTick);
|
|
|
|
InitializeAsync();
|
|
|
|
Application.logMessageReceivedThreaded += EnqueueLog;
|
|
}
|
|
|
|
private async void InitializeAsync()
|
|
{
|
|
var go = new GameObject("UXConsole");
|
|
Object.DontDestroyOnLoad(go);
|
|
var document = go.AddComponent<UIDocument>();
|
|
document.sortingOrder = 1;
|
|
try
|
|
{
|
|
var panelSettings =await ModService.LoadAsset<PanelSettings>("ux_panel_settings");
|
|
document.panelSettings = panelSettings;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
BIT4Log.Warning<UXService>("未找到ux_panel_settings");
|
|
throw;
|
|
}
|
|
document.visualTreeAsset = await ModService.LoadAsset<VisualTreeAsset>("ui_console");
|
|
|
|
_rootVisualElement = document.rootVisualElement;
|
|
|
|
UXUtils.Inject(this,_rootVisualElement);
|
|
|
|
_commandSelector = new()
|
|
{
|
|
Container = _commandContainer,
|
|
};
|
|
_commandSelector.OnSelected += x =>
|
|
{
|
|
_textField.SetValueWithoutNotify(x.Name);
|
|
_textField.Blur();
|
|
_textField.Focus();
|
|
};
|
|
_singleton = this;
|
|
|
|
_textField.RegisterValueChangedCallback(OnTextFieldValueChanged);
|
|
_textField.RegisterCallback<KeyDownEvent>(OnKeyDown);
|
|
|
|
_text.text = string.Empty;
|
|
|
|
Toggle(false);
|
|
}
|
|
|
|
private VisualElement _rootVisualElement;
|
|
|
|
private static bool _exceptionPrintStackTrace;
|
|
private class CommandSelector
|
|
{
|
|
public VisualElement Container { get; set; }
|
|
public int Index
|
|
{
|
|
get => _index;
|
|
set
|
|
{
|
|
if (_methods.Length is 0)
|
|
{
|
|
_index = -1;
|
|
return;
|
|
}
|
|
_index = (value+_methods.Length)%_methods.Length;
|
|
Rebuild();
|
|
}
|
|
}
|
|
public event Action<MethodInfo> OnSelected;
|
|
|
|
private int _index;
|
|
private MethodInfo[] _methods=Array.Empty<MethodInfo>();
|
|
public void Rebuild()
|
|
{
|
|
Container.Clear();
|
|
for (var i = 0; i < _methods.Length; i++)
|
|
{
|
|
var method = _methods[i];
|
|
|
|
var button = Container.Create<Button>();
|
|
|
|
StringBuilder stringBuilder = new(method.Name);
|
|
|
|
foreach (var parameterInfo in method.GetParameters())
|
|
{
|
|
stringBuilder.Append($" {parameterInfo.ParameterType.Name}:{parameterInfo.Name}");
|
|
}
|
|
|
|
button.clicked+= () =>
|
|
{
|
|
OnSelected?.Invoke(method);
|
|
Rebuild();
|
|
};
|
|
|
|
button.text = stringBuilder.ToString();
|
|
|
|
Container.Add(button);
|
|
}
|
|
|
|
}
|
|
public void SetMethods(IEnumerable<MethodInfo> methods)
|
|
{
|
|
if (methods.IsValid())
|
|
{
|
|
if (_methods.SequenceEqual(methods)) return;
|
|
}
|
|
|
|
_methods=methods.IsValid()?methods.ToArray():Array.Empty<MethodInfo>();
|
|
if (_methods!.Length is 0)
|
|
{
|
|
_index = -1;
|
|
}
|
|
Rebuild();
|
|
}
|
|
}
|
|
[BITCommand]
|
|
public static async void Clear()
|
|
{
|
|
await BITApp.SwitchToMainThread();
|
|
_singleton._outputString.Clear();
|
|
_singleton._text.text = string.Empty;
|
|
}
|
|
private static UXConsole _singleton;
|
|
|
|
private static readonly ConcurrentQueue<(string condition, string stackTrace, LogType type)> LOGQueue = new();
|
|
private static void EnqueueLog(string condition, string stackTrace, LogType type)
|
|
{
|
|
LOGQueue.Enqueue((condition, stackTrace, type));
|
|
}
|
|
|
|
private const int LOGLineLimit = 64;
|
|
|
|
[UXBindPath("commands-container")]
|
|
private VisualElement _commandContainer;
|
|
[UXBindPath("TextField")]
|
|
private TextField _textField;
|
|
[UXBindPath("Text")]
|
|
private Label _text;
|
|
[UXBindPath( "context-scrollview")]
|
|
private ScrollView _scrollView;
|
|
private bool _isRunning;
|
|
private List<string> _outputString = new();
|
|
|
|
private CommandSelector _commandSelector;
|
|
|
|
private void OnNextLine()
|
|
{
|
|
if (_outputString.Count is not 0 && _outputString.Last() != string.Empty)
|
|
_outputString.Add(string.Empty);
|
|
}
|
|
private void Toggle(bool active)
|
|
{
|
|
_commandSelector.SetMethods(null);
|
|
|
|
_rootVisualElement.SetActive(active);
|
|
_isRunning = active;
|
|
|
|
BITAppForUnity.AllowCursor.SetElements(this,active);
|
|
BITInputSystem.AllowInput.SetDisableElements(this,active);
|
|
|
|
if (active)
|
|
{
|
|
_textField.SetValueWithoutNotify(string.Empty);
|
|
|
|
_textField.Focus();
|
|
|
|
_scrollView.ScrollToBottom();
|
|
}
|
|
else
|
|
{
|
|
_text.Blur();
|
|
}
|
|
}
|
|
private void OnTextFieldValueChanged(ChangeEvent<string> callback)
|
|
{
|
|
if (string.IsNullOrEmpty(callback.newValue) is false)
|
|
{
|
|
var commands = BITCommands.GetMethodInfos(callback.newValue).ToArray();
|
|
|
|
_commandSelector.SetMethods(commands);
|
|
|
|
_commandContainer.SetActive(commands.Length is not 0);
|
|
}
|
|
else
|
|
{
|
|
_commandContainer.SetActive(false);
|
|
|
|
_commandSelector.SetMethods(null);
|
|
}
|
|
}
|
|
private bool _stopNextFrame;
|
|
private void OnKeyDown(KeyDownEvent keyDownEvent)
|
|
{
|
|
if (_stopNextFrame)
|
|
{
|
|
keyDownEvent.StopPropagation();
|
|
keyDownEvent.PreventDefault();
|
|
_stopNextFrame = false;
|
|
}
|
|
|
|
|
|
if (keyDownEvent.keyCode is KeyCode.BackQuote)
|
|
{
|
|
keyDownEvent.StopPropagation();
|
|
keyDownEvent.PreventDefault();
|
|
_stopNextFrame = true;
|
|
return;
|
|
}
|
|
switch (keyDownEvent.keyCode)
|
|
{
|
|
case KeyCode.Return:
|
|
var cmd = _textField.text;
|
|
|
|
LogCallback($">{cmd}", string.Empty, LogType.Log);
|
|
|
|
_textField.SetValueWithoutNotify(string.Empty);
|
|
|
|
BITCommands.Excute(cmd);
|
|
|
|
_commandSelector.SetMethods(null);
|
|
|
|
keyDownEvent.StopPropagation();
|
|
keyDownEvent.PreventDefault();
|
|
_stopNextFrame = true;
|
|
break;
|
|
case KeyCode.Tab:
|
|
keyDownEvent.StopPropagation();
|
|
keyDownEvent.PreventDefault();
|
|
break;
|
|
case KeyCode.DownArrow when string.IsNullOrEmpty(_textField.text) is false:
|
|
_commandSelector.Index-=1;
|
|
keyDownEvent.StopPropagation();
|
|
keyDownEvent.PreventDefault();
|
|
break;
|
|
case KeyCode.UpArrow when string.IsNullOrEmpty(_textField.text) is false:
|
|
_commandSelector.Index+=1;
|
|
keyDownEvent.StopPropagation();
|
|
keyDownEvent.PreventDefault();
|
|
break;
|
|
}
|
|
|
|
}
|
|
private async void LogCallback(string condition, string stackTrace, LogType type)
|
|
{
|
|
try
|
|
{
|
|
switch (type)
|
|
{
|
|
case LogType.Error:
|
|
_outputString.Add($"<color=red>{condition}</color>");
|
|
break;
|
|
case LogType.Warning:
|
|
_outputString.Add($"<color=yellow>{condition}</color>");
|
|
break;
|
|
case LogType.Exception:
|
|
_outputString.Add($"<color=red>{condition}</color>");
|
|
if (_exceptionPrintStackTrace)
|
|
_outputString.Add($"<color=red>{stackTrace}</color>");
|
|
break;
|
|
default:
|
|
_outputString.Add(condition);
|
|
break;
|
|
}
|
|
|
|
var length = _outputString.Count;
|
|
if (length > LOGLineLimit)
|
|
{
|
|
_outputString = _outputString.GetRange(length - LOGLineLimit, LOGLineLimit);
|
|
}
|
|
StringBuilder stringBuilder = new();
|
|
_outputString.ForEach(x => stringBuilder.AppendLine(x));
|
|
try
|
|
{
|
|
await BITApp.SwitchToMainThread();
|
|
_scrollView.ScrollToBottomAutomatic();
|
|
_text.text = stringBuilder.ToString();
|
|
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogException(e);
|
|
}
|
|
}
|
|
|
|
private void OnTick(float delta)
|
|
{
|
|
if (_rootVisualElement is null) return;
|
|
|
|
if (Keyboard.current is { backquoteKey: { wasPressedThisFrame: true } })
|
|
{
|
|
Toggle(_isRunning=!_isRunning);
|
|
return;
|
|
}
|
|
|
|
if(LOGQueue.TryDequeue(out var log))
|
|
{
|
|
LogCallback(log.condition, log.stackTrace, log.type);
|
|
}
|
|
|
|
if (_isRunning is false) return;
|
|
|
|
var pos = _textField.worldTransform.GetPosition();
|
|
var size = _textField.layout.size;
|
|
|
|
_commandContainer.style.left = 0;
|
|
_commandContainer.style.top = 0;
|
|
|
|
pos.y += size.y;
|
|
_commandContainer.transform.position = pos;
|
|
|
|
_commandContainer.style.width = size.x;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_ticker.Remove(OnTick);
|
|
Application.logMessageReceivedThreaded -= EnqueueLog;
|
|
}
|
|
}
|
|
} |