This commit is contained in:
CortexCore 2025-01-12 11:13:19 +08:00
parent 01e7e4e35e
commit 01b3d1be43
26 changed files with 387 additions and 336 deletions

View File

@ -8,19 +8,15 @@ namespace BITKit;
public class BITAppForNet public class BITAppForNet
{ {
[Obsolete("Use InitializeAsync instead")] private static readonly Timer _timer = new();
public static UniTask Init(string name)=>UniTask.CompletedTask;
private static Timer _timer = new(); private static readonly DateTime _startTime = DateTime.UtcNow;
private static DateTime _startTime = DateTime.UtcNow;
public static async UniTask InitializeAsync(string name) public static async UniTask InitializeAsync(string name)
{ {
BIT4Log.OnLog += Console.WriteLine; BIT4Log.OnLog += Console.WriteLine;
BIT4Log.OnWarning += Console.WriteLine; BIT4Log.OnWarning += Console.WriteLine;
BIT4Log.OnException += e => Console.WriteLine(e.ToString()); BIT4Log.OnException += e => Console.WriteLine(e.ToString());
BIT4Log.OnSetConsoleColor += color => Console.ForegroundColor = color;
BIT4Log.OnNextLine += Console.WriteLine; BIT4Log.OnNextLine += Console.WriteLine;

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
using UnityEngine; using UnityEngine;
#endif #endif
@ -15,44 +17,40 @@ namespace BITKit
OnLog = null; OnLog = null;
OnException = null; OnException = null;
OnWarning = null; OnWarning = null;
OnSetConsoleColor = null;
OnNextLine = null; OnNextLine = null;
} }
#endif #endif
public static event Action<string> OnLog; public static event Action<string> OnLog;
public static event Action<string,Type> OnLogCallback;
public static event Action<Exception> OnException; public static event Action<Exception> OnException;
public static event Action<Exception,Type> OnExceptionCallback;
public static event Action<string> OnWarning; public static event Action<string> OnWarning;
public static event Action<string,Type> OnWarningCallback;
public static event Action<ConsoleColor> OnSetConsoleColor;
public static event Action OnNextLine; public static event Action OnNextLine;
private static Type currentType;
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
//[HideInCallstack] [HideInCallstack]
#endif #endif
public static void Log(object x, ConsoleColor color = ConsoleColor.White) public static void Log(object x)
{ {
OnSetConsoleColor?.Invoke(color); if (OnLog is null)
OnLog?.Invoke(x?.ToString()); {
OnLogCallback?.Invoke(x?.ToString(),currentType); BITApp.ServiceProvider.GetRequiredService<ILogger<BITApp>>().LogInformation(x.ToString());
}
else
{
OnLog?.Invoke(x?.ToString());
}
} }
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
[HideInCallstack] [HideInCallstack]
#endif #endif
public static void Log<T>(object x, ConsoleColor color = ConsoleColor.White) public static void Log<T>(object x)
{ {
if (currentType != typeof(T)) if (OnLog is null)
{ {
OnNextLine?.Invoke(); BITApp.ServiceProvider.GetRequiredService<ILogger<T>>().LogInformation(x.ToString());
}
else
{
OnLog?.Invoke(x?.ToString());
} }
#if NET5_0_OR_GREATER
Log($"[{DateTime.Now}]{typeof(T).Name}:{x}");
#else
Log($"<color=#add8e6ff><b>{typeof(T).Name}</b></color>:{x}");
#endif
currentType = typeof(T);
} }
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
[HideInCallstack] [HideInCallstack]

View File

@ -126,7 +126,12 @@ namespace BITKit
} }
catch (Exception e) catch (Exception e)
{ {
BIT4Log.LogException(e); #if UNITY_EDITOR
UnityEngine.Debug.LogException(e);
#else
BIT4Log.LogException(e);
#endif
} }
} }

View File

@ -553,7 +553,6 @@ namespace BITKit.Net
} }
if ((_now - startTime).TotalSeconds > RpcTimeOut) if ((_now - startTime).TotalSeconds > RpcTimeOut)
{ {
await BITApp.SwitchToMainThread();
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
throw new TimeoutException("请求超时或已断开连接"); throw new TimeoutException("请求超时或已断开连接");
@ -561,7 +560,6 @@ namespace BITKit.Net
} }
throw new TimeoutException($"请求超时或已断开连接,请求为{path}"); throw new TimeoutException($"请求超时或已断开连接,请求为{path}");
} }
if (_p2p.TryRemove(id, out var value)) if (_p2p.TryRemove(id, out var value))
{ {

View File

@ -62,7 +62,7 @@ namespace BITKit.Net
KCPNet.Config KCPNet.Config
); );
_timer.Elapsed += Tick; _timer.Elapsed += Tick;
BIT4Log.Log<KCPNetServer>("已创建KCP服务器"); //BIT4Log.Log<KCPNetServer>("已创建KCP服务器");
AddCommandListener<SimplePing>(F); AddCommandListener<SimplePing>(F);

View File

@ -130,10 +130,6 @@ namespace BITKit.Mod
{ {
public static async UniTask<ModPackage[]> SearchPackages() public static async UniTask<ModPackage[]> SearchPackages()
{ {
//Todo
IUXWaiting waiting = null;
var handle = waiting?.Get();
handle?.SetMessage("正在搜索Mod包");
var list=new List<ModPackage>(); var list=new List<ModPackage>();
var path = Path.Combine(Environment.CurrentDirectory, "Mods"); var path = Path.Combine(Environment.CurrentDirectory, "Mods");
@ -149,7 +145,6 @@ namespace BITKit.Mod
list.Add(package); list.Add(package);
} }
waiting?.Release(handle);
return list.ToArray(); return list.ToArray();
} }
public static async UniTask Reload() public static async UniTask Reload()
@ -184,16 +179,7 @@ namespace BITKit.Mod
public static IMod[] Mods { get; private set; }=Array.Empty<IMod>(); public static IMod[] Mods { get; private set; }=Array.Empty<IMod>();
public static bool IsLocked public static readonly ValidHandle IsBusy = new();
{
get => _IsLocked;
set
{
if (_IsLocked == value) return;
_IsLocked = value;
OnLocked?.Invoke(value);
}
}
public static event Action<ModPackage> OnPackageLoad; public static event Action<ModPackage> OnPackageLoad;
public static event Action<ModPackage> OnPackageLoaded; public static event Action<ModPackage> OnPackageLoaded;
@ -207,179 +193,72 @@ namespace BITKit.Mod
public static event Action OnReload; public static event Action OnReload;
public static event Action OnReloaded; public static event Action OnReloaded;
public static event Action<bool> OnLocked;
public static event Func<IMod,UniTask> OnModLoadAsync; public static event Func<IMod,UniTask> OnModLoadAsync;
public static event Func<IMod,UniTask> OnModUnloadAsync; public static event Func<IMod,UniTask> OnModUnloadAsync;
private static CancellationTokenSource _CancellationTokenSource; private static CancellationTokenSource _CancellationTokenSource;
private static readonly ConcurrentQueue<IMod> _RegisterQueue=new();
private static readonly ConcurrentQueue<IMod> _UnRegisterQueue = new();
private static readonly List<IMod> _CacheMods = new();
private static readonly ConcurrentDictionary<string,IMod> _InstalledMods=new(); private static readonly ConcurrentDictionary<string,IMod> _InstalledMods=new();
private static Thread _Thread;
private static bool _IsRunning;
private static bool _IsLocked;
public static void Initialize() public static async UniTask Initialize()
{ {
BIT4Log.Log<ModService>("Mod服务已启动"); BIT4Log.Log<ModService>("Mod服务已启动");
_IsRunning = true;
_CancellationTokenSource = new CancellationTokenSource(); _CancellationTokenSource = new CancellationTokenSource();
_Thread = new Thread(InternalInitialize);
_Thread.Start();
return; try
async void InternalInitialize()
{ {
try try
{ {
var modPath = Path.Combine(Environment.CurrentDirectory, "Mods\\");
PathHelper.EnsureDirectoryCreated(modPath);
var directoryInfo = new DirectoryInfo(modPath);
try foreach (var fileInfo in directoryInfo.GetFiles())
{ {
var modPath = Path.Combine(Environment.CurrentDirectory, "Mods\\"); switch (fileInfo.Extension)
PathHelper.EnsureDirectoryCreated(modPath);
var directoryInfo = new DirectoryInfo(modPath);
foreach (var fileInfo in directoryInfo.GetFiles())
{ {
switch (fileInfo.Extension) case ".dll":
{ {
case ".dll": var assembly = Assembly.LoadFile(fileInfo.FullName);
{ await Load(assembly);
var assembly = Assembly.LoadFile(fileInfo.FullName); continue;
await Load(assembly); }
continue;
}
#if UNITY_5_3_OR_NEWER #if UNITY_5_3_OR_NEWER
case ".cs": case ".cs":
{
var code = await File.ReadAllTextAsync(fileInfo.FullName);
var assembly = BITSharp.Compile(code);
await Load(assembly, fileInfo.DirectoryName);
continue;
}
#endif
}
}
}
catch (Exception e)
{
BIT4Log.Warning<ModService>("自动加载Mod失败");
BIT4Log.LogException(e);
}
while (_IsRunning)
{
//todo
IUXWaiting waiting = null;
_CacheMods.Clear();
while (_UnRegisterQueue.TryDequeue(out var mod))
{
var handle = waiting?.Get();
handle?.SetMessage($":正在卸载{mod.PackageName}");
mod.OnDispose();
_CacheMods.Add(mod);
OnModUnLoad?.Invoke(mod);
waiting?.Release(handle);
}
foreach (var mod in _CacheMods)
{
await mod.OnDisposeAsync(_CancellationTokenSource.Token);
foreach (var x in OnModUnloadAsync.CastAsFunc())
{ {
await x.Invoke(mod); var code = await File.ReadAllTextAsync(fileInfo.FullName);
var assembly = BITSharp.Compile(code);
await Load(assembly, fileInfo.DirectoryName);
continue;
} }
}
foreach (var mod in _CacheMods)
{
mod.OnDisposed();
OnModUnLoaded?.Invoke(mod);
BIT4Log.Log<ModService>($"卸载Mod:{mod.GetType().FullName}");
}
_CacheMods.Clear();
while (_RegisterQueue.TryDequeue(out var mod))
{
var handle = waiting?.Get();
handle?.SetMessage($"正在加载:{mod.PackageName}");
_CacheMods.Add(mod);
mod.OnInitialize();
OnModLoad?.Invoke(mod);
BIT4Log.Log<ModService>($"加载Mod:{mod.GetType().FullName}");
waiting?.Release(handle);
}
foreach (var mod in _CacheMods)
{
var handle = waiting?.Get();
handle?.SetMessage($"正在初始化:{mod.PackageName}");
await mod.OnInitializedAsync(_CancellationTokenSource.Token);
foreach (var x in OnModLoadAsync.CastAsFunc())
{
await x.Invoke(mod);
}
waiting?.Release(handle);
}
foreach (var mod in _CacheMods)
{
var handle = waiting?.Get();
handle?.SetMessage($":正在完成初始化中{mod.PackageName}");
mod.OnInitialized();
OnModLoaded?.Invoke(mod);
waiting?.Release(handle);
}
_CacheMods.Clear();
//Thread.Sleep(1000);
#if UNITY_5_3_OR_NEWER
await UniTask.Delay(1000);
#else
await Task.Delay(1000);
#endif #endif
IsLocked = false; }
} }
BIT4Log.Log<ModService>("Mod服务已停止");
} }
catch (Exception e) catch (Exception e)
{ {
BIT4Log.Warning<ModService>("自动加载Mod失败");
BIT4Log.LogException(e); BIT4Log.LogException(e);
BIT4Log.Warning<ModService>("Mod服务遇到了错误,已停止");
} }
} }
catch (Exception e)
{
BIT4Log.LogException(e);
BIT4Log.Warning<ModService>("Mod服务遇到了错误,已停止");
}
} }
public static void Dispose() public static void Dispose()
{_IsRunning = false; {
_CancellationTokenSource.Cancel(); _CancellationTokenSource.Cancel();
try try
{ {
_Thread.Join(100);
_RegisterQueue.Clear();
_UnRegisterQueue.Clear();
Mods = Array.Empty<IMod>(); Mods = Array.Empty<IMod>();
_InstalledMods.Clear(); _InstalledMods.Clear();
} }
@ -387,8 +266,9 @@ namespace BITKit.Mod
{ {
BIT4Log.LogException(e); BIT4Log.LogException(e);
} }
} }
public static UniTask Load(Assembly assembly,string folderPath=null) public static UniTask Load(Assembly assembly,string folderPath=null)
{ {
BIT4Log.Log<ModService>($"加载程序集:{assembly.FullName}"); BIT4Log.Log<ModService>($"加载程序集:{assembly.FullName}");
@ -480,20 +360,35 @@ namespace BITKit.Mod
} }
OnPackageLoaded?.Invoke(package); OnPackageLoaded?.Invoke(package);
} }
public static void Load(IMod mod) public static async UniTask Load(IMod mod)
{ {
IsLocked = true; mod.OnInitialize();
_RegisterQueue.Enqueue(mod); OnModLoad?.Invoke(mod);
BIT4Log.Log<ModService>($"加载Mod:{mod.GetType().FullName}");
} }
public static void UnLoad(IMod mod) public static async UniTask UnLoad(IMod mod)
{ {
IsLocked = true; mod.OnDispose();
_UnRegisterQueue.Enqueue(mod); OnModUnLoad?.Invoke(mod);
await mod.OnDisposeAsync(_CancellationTokenSource.Token);
foreach (var x in OnModUnloadAsync.CastAsFunc())
{
await x.Invoke(mod);
}
mod.OnDisposed();
OnModUnLoaded?.Invoke(mod);
BIT4Log.Log<ModService>($"卸载Mod:{mod.GetType().FullName}");
} }
public static void Install(IMod mod) public static async void Install(IMod mod)
{ {
await IsBusy;
using var _ = IsBusy.GetHandle();
if (_InstalledMods.ContainsKey(mod.PackageName)) if (_InstalledMods.ContainsKey(mod.PackageName))
{ {
throw new ArgumentException("Mod已安装"); throw new ArgumentException("Mod已安装");
@ -501,9 +396,21 @@ namespace BITKit.Mod
_InstalledMods.TryAdd(mod.PackageName,mod); _InstalledMods.TryAdd(mod.PackageName,mod);
Mods = _InstalledMods.Values.ToArray(); Mods = _InstalledMods.Values.ToArray();
OnModInstalled?.Invoke(mod); OnModInstalled?.Invoke(mod);
await mod.OnInitializedAsync(_CancellationTokenSource.Token);
foreach (var x in OnModLoadAsync.CastAsFunc())
{
await x.Invoke(mod);
}
mod.OnInitialized();
OnModLoaded?.Invoke(mod);
} }
public static void UnInstall(IMod mod) public static void UnInstall(IMod mod)
{ {
using var _ = IsBusy.GetHandle();
if(_InstalledMods.ContainsKey(mod.PackageName) is false) return; if(_InstalledMods.ContainsKey(mod.PackageName) is false) return;
_InstalledMods.TryRemove(mod.PackageName); _InstalledMods.TryRemove(mod.PackageName);
Mods = _InstalledMods.Values.ToArray(); Mods = _InstalledMods.Values.ToArray();

View File

@ -103,10 +103,11 @@ namespace BITKit
if (IsSyncContext) if (IsSyncContext)
{ {
await BITApp.SwitchToMainThread(); await BITApp.SwitchToMainThread();
#if UNITY_EDITOR #if UNITY_EDITOR
await BITApp.SwitchToMainThread();
if (UnityEditor.EditorApplication.isPaused) if (UnityEditor.EditorApplication.isPaused)
{ {
_timer.Interval = 1000d / TickRate;
_timer.Start(); _timer.Start();
return; return;
} }
@ -164,12 +165,19 @@ namespace BITKit
TickCount++; TickCount++;
if(_isDisposed)return; if(_isDisposed)return;
_timer.Interval = 1000d / TickRate;
_timer.Start(); _timer.Start();
} }
public bool IsSyncContext { get; set; } = true; public bool IsSyncContext { get; set; } = true;
public ulong TickCount { get; set; } public ulong TickCount { get; set; }
public int TickRate { get; set; }
public int TickRate
{
get => _tickRate;
set => _tickRate = Math.Clamp(value, 1, int.MaxValue);
}
private int _tickRate;
public bool IsConcurrent { get; set; } public bool IsConcurrent { get; set; }
public event Func<float, UniTask> OnTickAsync; public event Func<float, UniTask> OnTickAsync;

View File

@ -8,6 +8,7 @@ namespace BITKit.UX
/// </summary> /// </summary>
public interface IUXService:IDisposable public interface IUXService:IDisposable
{ {
public string SettingsPath { get; set; }
object Root { get; } object Root { get; }
/// <summary> /// <summary>
/// 初始化 /// 初始化

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
@ -87,7 +88,7 @@ namespace BITKit
public readonly List<object> disableObjs = new List<object>(); public readonly List<object> disableObjs = new List<object>();
private bool tempEnable; private bool tempEnable;
private Action<bool> EventOnEnableChanged; private Action<bool> EventOnEnableChanged;
private readonly Queue<UniTaskCompletionSource> _completionSources = new(); private readonly ConcurrentQueue<UniTaskCompletionSource> _completionSources = new();
private bool _isDisposed; private bool _isDisposed;
public void AddElement(object obj) public void AddElement(object obj)

View File

@ -228,9 +228,24 @@ namespace BITKit.Console
_commandSelector.SetMethods(null); _commandSelector.SetMethods(null);
} }
} }
private async void OnKeyDown(KeyDownEvent keyDownEvent) private bool _stopNextFrame;
private void OnKeyDown(KeyDownEvent keyDownEvent)
{ {
var nextStop=true; if (_stopNextFrame)
{
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
_stopNextFrame = false;
}
if (keyDownEvent.keyCode is KeyCode.BackQuote)
{
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
_stopNextFrame = true;
return;
}
switch (keyDownEvent.keyCode) switch (keyDownEvent.keyCode)
{ {
case KeyCode.Return: case KeyCode.Return:
@ -240,35 +255,30 @@ namespace BITKit.Console
_textField.SetValueWithoutNotify(string.Empty); _textField.SetValueWithoutNotify(string.Empty);
await UniTask.NextFrame();
_textField.Blur();
_textField.Focus();
BITCommands.Excute(cmd); BITCommands.Excute(cmd);
_commandSelector.SetMethods(null); _commandSelector.SetMethods(null);
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
_stopNextFrame = true;
break; break;
case KeyCode.Tab: case KeyCode.Tab:
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
break; break;
case KeyCode.DownArrow when string.IsNullOrEmpty(_textField.text) is false: case KeyCode.DownArrow when string.IsNullOrEmpty(_textField.text) is false:
_commandSelector.Index-=1; _commandSelector.Index-=1;
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
break; break;
case KeyCode.UpArrow when string.IsNullOrEmpty(_textField.text) is false: case KeyCode.UpArrow when string.IsNullOrEmpty(_textField.text) is false:
_commandSelector.Index+=1; _commandSelector.Index+=1;
break; keyDownEvent.StopPropagation();
default: keyDownEvent.PreventDefault();
nextStop = false;
break; break;
} }
if (nextStop)
{
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
}
} }
private async void LogCallback(string condition, string stackTrace, LogType type) private async void LogCallback(string condition, string stackTrace, LogType type)
{ {

View File

@ -40,6 +40,7 @@ namespace BITKit
} }
public sealed class UnityLogger<T>:ILogger<T> public sealed class UnityLogger<T>:ILogger<T>
{ {
[HideInCallstack]
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{ {
switch (logLevel) switch (logLevel)
@ -56,7 +57,7 @@ namespace BITKit
} }
break; break;
default: default:
Debug.Log($"<color=cyan>{typeof(T).Name}</color>:{state.ToString()}"); Debug.Log($"<b>{typeof(T).Name}</b>:{state.ToString()}");
break; break;
} }
} }

View File

@ -75,7 +75,7 @@ namespace BITKit
private void LateUpdate() private void LateUpdate()
{ {
LateUpdateTick?.Invoke(Time.deltaTime); LateUpdateTick?.Invoke(Time.deltaTime);
} }
} }

View File

@ -5,6 +5,7 @@ using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using BITKit.Mod; using BITKit.Mod;
using Cysharp.Threading.Tasks;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace BITKit.UX.Internal namespace BITKit.UX.Internal
@ -111,7 +112,7 @@ namespace BITKit.UX
private readonly IUXService _uxService; private readonly IUXService _uxService;
private VisualElement _root; private VisualElement _root;
private VisualElement _container; private VisualElement _container;
private bool _isInitialized = false; private readonly UniTaskCompletionSource _waitUntilInitialized = new();
public UXContextMenu(IUXService uxService) public UXContextMenu(IUXService uxService)
{ {
_uxService = uxService; _uxService = uxService;
@ -144,11 +145,14 @@ namespace BITKit.UX
{ {
Close(); Close();
}); });
_isInitialized = true;
_waitUntilInitialized.TrySetResult();
Close(); Close();
} }
public void Create(ContextMenuBuilder builder) public async void Create(ContextMenuBuilder builder)
{ {
await _waitUntilInitialized.Task;
var pos = Mouse.current.position.ReadValue(); var pos = Mouse.current.position.ReadValue();
pos.y = Screen.height - pos.y; pos.y = Screen.height - pos.y;
pos = RuntimePanelUtils.ScreenToPanel(_root.panel, pos); pos = RuntimePanelUtils.ScreenToPanel(_root.panel, pos);
@ -166,9 +170,9 @@ namespace BITKit.UX
} }
builder.OnExecuted += Close; builder.OnExecuted += Close;
} }
private void Close() private async void Close()
{ {
if(_isInitialized is false)return; await _waitUntilInitialized.Task;
_container.Clear(); _container.Clear();
_root.SetActive(false); _root.SetActive(false);
} }

View File

@ -106,6 +106,7 @@ namespace BITKit.UX
var tabName = split[i]; var tabName = split[i];
var index = i; var index = i;
var button = _buttons[i] = this.Create<Button>(); var button = _buttons[i] = this.Create<Button>();
button.AddToClassList("v"+i);
button.text = tabName; button.text = tabName;
button.focusable = allowFocus; button.focusable = allowFocus;
button.clicked += () => CurrentTab = index; button.clicked += () => CurrentTab = index;

View File

@ -25,50 +25,48 @@ namespace BITKit.UX
public new class UxmlFactory : UxmlFactory<TabContainer, UxmlTraits> { } public new class UxmlFactory : UxmlFactory<TabContainer, UxmlTraits> { }
public TabContainer() public TabContainer()
{ {
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel); RegisterCallback<AttachToPanelEvent>(RebuildOnEvent);
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel); RegisterCallback<DetachFromPanelEvent>(RebuildOnEvent);
RegisterCallback<GeometryChangedEvent>(OnGeometryChanged); RegisterCallback<GeometryChangedEvent>(RebuildOnEvent);
RegisterCallback<BlurEvent>(RebuildOnEvent);
RegisterCallback<FocusEvent>(RebuildOnEvent);
} }
private void OnGeometryChanged(GeometryChangedEvent evt)
{
Rebuild();
}
public string TabPath public string TabPath
{ {
get=>_tabPath; get=>_tabPath;
set set
{ {
_tabPath = value; _tabPath = value;
Rebuild();
} }
} }
private string _tabPath; private string _tabPath;
private TabBar _tabBar; private TabBar _tabBar;
private int _index; private int _index;
private void OnDetachFromPanel(DetachFromPanelEvent evt) private void RebuildOnEvent<T>(T evt)
{ {
Rebuild(); Rebuild();
} }
private void OnAttachToPanel(AttachToPanelEvent evt)
{
Rebuild();
}
private void Rebuild() private void Rebuild()
{ {
if (_tabBar is not null) if (_tabBar is not null)
{ {
_tabBar.OnTabChanged -= OnTabChanged; _tabBar.OnTabChanged -= OnTabChanged;
} }
_tabBar = panel.visualTree.Q<TabBar>(TabPath); var p = parent;
while (p is not null)
{
_tabBar = p.Q<TabBar>(TabPath);
if (_tabBar is not null)
{
break;
}
p = p.parent;
}
if (_tabBar is not null) if (_tabBar is not null)
{ {
_tabBar.OnTabChanged += OnTabChanged; _tabBar.OnTabChanged += OnTabChanged;
} _index = _tabBar.CurrentTab;
if (_index <=0)
{
_index = 1;
} }
OnTabChanged(_index); OnTabChanged(_index);
} }
@ -76,7 +74,7 @@ namespace BITKit.UX
private void OnTabChanged(int obj) private void OnTabChanged(int obj)
{ {
_index = obj; _index = obj;
if (childCount <= 0) return; if (childCount < 0) return;
for (var i = 0; i < childCount; i++) for (var i = 0; i < childCount; i++)
{ {
var visualElement = this[i]; var visualElement = this[i];

View File

@ -21,16 +21,10 @@ namespace BITKit.UX
private const string TemplatePath = "ux_mod_service_template"; private const string TemplatePath = "ux_mod_service_template";
public override bool AllowCursor => true; public override bool AllowCursor => true;
public UXModService(IUXService uxService) : base(uxService)
{
ModService.OnModInstalled+=OnModInstalled;
ModService.OnModUnInstalled+=OnModUnInstalled;
ModService.OnModLoaded+=OnModLoaded;
ModService.OnModUnLoaded+=OnModUnLoaded;
ModService.OnLocked+=OnLocked;
}
private VisualTreeAsset _modTemplate; private VisualTreeAsset _modTemplate;
[UXBindPath("open_folder-button")]
private Button _openFolderButton;
[UXBindPath("open-mod-button")] [UXBindPath("open-mod-button")]
private Button _openModButton; private Button _openModButton;
[UXBindPath("mods-container")] [UXBindPath("mods-container")]
@ -43,22 +37,27 @@ namespace BITKit.UX
private Label _modDescriptionLabel; private Label _modDescriptionLabel;
[UXBindPath("reload-mod-button",true)] [UXBindPath("reload-mod-button",true)]
private Button _reloadModButton; private Button _reloadModButton;
[UXBindPath("install-roslyn-fill")]
private VisualElement _installRoslynFill;
private readonly ConcurrentDictionary<string,VisualElement> _modContainers=new(); private readonly ConcurrentDictionary<string,VisualElement> _modContainers=new();
public UXModService(IUXService uxService) : base(uxService)
{
ModService.OnModInstalled+=OnModInstalled;
ModService.OnModUnInstalled+=OnModUnInstalled;
ModService.OnModLoaded+=OnModLoaded;
ModService.OnModUnLoaded+=OnModUnLoaded;
ModService.IsBusy.AddListener(OnLocked);
private void OnLocked(bool obj) OnInitiatedAsync += InitializeAsync;
{
RootVisualElement?.SetEnabled(!obj);
} }
public override async UniTask EntryAsync()
private async UniTask InitializeAsync()
{ {
await base.EntryAsync(); _installRoslynFill.style.width = 0;
_modTemplate =await ModService.LoadAsset<VisualTreeAsset>(TemplatePath); _modTemplate =await ModService.LoadAsset<VisualTreeAsset>(TemplatePath);
UXUtils.Inject(this); UXUtils.Inject(this);
if (_openModButton is not null)
{
_openModButton.clicked += OpenMod;
}
if (_returnButton is not null) if (_returnButton is not null)
{ {
@ -79,7 +78,22 @@ namespace BITKit.UX
await UniTask.SwitchToMainThread(); await UniTask.SwitchToMainThread();
_reloadModButton.SetEnabled(true); _reloadModButton.SetEnabled(true);
}; };
_openFolderButton.clicked += () =>
{
new BITApp.OpenPath()
{
path = Path.Combine(Environment.CurrentDirectory, "Mods")
}.Execute();
};
} }
private void OnLocked(bool obj)
{
RootVisualElement?.SetEnabled(!obj);
}
private async void OnModUnInstalled(IMod obj) private async void OnModUnInstalled(IMod obj)
{ {
await UniTask.SwitchToMainThread(); await UniTask.SwitchToMainThread();
@ -117,78 +131,6 @@ namespace BITKit.UX
var container = _modContainers.GetOrAdd(obj.Name,_=> Create(obj)); var container = _modContainers.GetOrAdd(obj.Name,_=> Create(obj));
container.Get<Toggle>().SetValueWithoutNotify(true); container.Get<Toggle>().SetValueWithoutNotify(true);
} }
private static void OpenMod()
{
BIT4Log.Log("正在打开选择文件对话框");
#if UNITY_EDITOR
new Thread(OpenModInternal).Start();
#else
OpenModInternal();
#endif
return;
void OpenModInternal()
{
#if UNITY_5_3_OR_NEWER && UNITY_WINDOW
BIT4Log.Log<UXModService>("已进入文件对话框线程");
new FileBrowser().OpenFileBrowser(new BrowserProperties()
{
//filterIndex = 0,
//filter = "C Sharp files (*.cs)|*.cs |Dll files (*.dll)|*.dll",
}, Filepath);
#else
throw new NotSupportedException($"{Application.platform} 不支持打开文件对话框");
#endif
return;
async void Filepath(string path)
{
BIT4Log.Log<UXModService>("已选择文件:"+path);
await BITApp.SwitchToMainThread();
try
{
var file = new FileInfo(path);
switch (file.Extension)
{
case ".cs":
#if UNITY_5_3_OR_NEWER
var code = await File.ReadAllTextAsync(path);
var assembly = BITSharp.Compile(code);
await ModService.Load(assembly,Path.GetDirectoryName(path));
#else
throw new NotSupportedException($"{Application.platform} 不支持编译C#代码");
#endif
break;
case ".dll":
var bytes = await File.ReadAllBytesAsync(path);
await ModService.Load(Assembly.Load(bytes),Path.GetDirectoryName(path));
break;
case ".json" when file.Name is ModPackage.DefaultFileName:
await ModService.LoadFromPackage(path);
break;
default:
Alert.Print(new AlertMessage()
{
title = "加载失败",
message = "不支持的文件类型"
});
break;
}
}
catch (Exception e)
{
Alert.Print(new AlertMessage()
{
title = "加载失败",
message = e.Message
});
BIT4Log.LogException(e);
}
}
}
}
private VisualElement Create(IMod mod) private VisualElement Create(IMod mod)
{ {
var container =_modsContainer.Create(_modTemplate); var container =_modsContainer.Create(_modTemplate);
@ -219,7 +161,7 @@ namespace BITKit.UX
ModService.OnModUnInstalled-=OnModUnInstalled; ModService.OnModUnInstalled-=OnModUnInstalled;
ModService.OnModLoaded-=OnModLoaded; ModService.OnModLoaded-=OnModLoaded;
ModService.OnModUnLoaded-=OnModUnLoaded; ModService.OnModUnLoaded-=OnModUnLoaded;
ModService.OnLocked-=OnLocked; ModService.IsBusy.RemoveListener(OnLocked);
} }
} }

View File

@ -18,7 +18,7 @@ using Debug = UnityEngine.Debug;
namespace BITKit.UX namespace BITKit.UX
{ {
public abstract class UIToolKitPanel : IUXPanel public abstract class UIToolKitPanel : IUXPanel,IDisposable
{ {
public const string USSEntry = "transition_entry"; public const string USSEntry = "transition_entry";
public const string USSEntryAsync = "transition_entry_async"; public const string USSEntryAsync = "transition_entry_async";
@ -128,6 +128,7 @@ namespace BITKit.UX
public virtual bool AllowCursor { get; } public virtual bool AllowCursor { get; }
public virtual bool AllowInput { get; } public virtual bool AllowInput { get; }
public virtual string[] InitialUssClasses { get; } = Array.Empty<string>(); public virtual string[] InitialUssClasses { get; } = Array.Empty<string>();
public bool IsDisposed { get; private set; }
public bool IsEntered { get; set; } public bool IsEntered { get; set; }
protected virtual void OnReturn() protected virtual void OnReturn()
@ -247,6 +248,12 @@ namespace BITKit.UX
public virtual void OnTick(float deltaTime) public virtual void OnTick(float deltaTime)
{ {
} }
public void Dispose()
{
RootVisualElement?.RemoveFromHierarchy();
IsDisposed = true;
}
} }
} }

View File

@ -13,6 +13,7 @@ namespace BITKit.UX
protected readonly IUXService UXService; protected readonly IUXService UXService;
protected VisualElement RootVisualElement { get; set; } protected VisualElement RootVisualElement { get; set; }
protected bool IsInitialized { get; private set; } protected bool IsInitialized { get; private set; }
protected readonly UniTaskCompletionSource WaitUntilInitialized = new();
protected UIToolkitOverlay(IUXService uxService, CancellationTokenSource cancellationToken) protected UIToolkitOverlay(IUXService uxService, CancellationTokenSource cancellationToken)
{ {
UXService = uxService; UXService = uxService;
@ -40,6 +41,8 @@ namespace BITKit.UX
UXUtils.Inject(this); UXUtils.Inject(this);
IsInitialized = true; IsInitialized = true;
WaitUntilInitialized.TrySetResult();
} }
public virtual void Dispose() public virtual void Dispose()
{ {

View File

@ -71,6 +71,7 @@ namespace BITKit.UX
/// </summary> /// </summary>
public static void ClearHistory() => History.Clear(); public static void ClearHistory() => History.Clear();
public string SettingsPath { get; set; } = "ux_panel_settings";
public object Root { get; private set; } public object Root { get; private set; }
public async UniTask InitializeAsync() public async UniTask InitializeAsync()
{ {
@ -86,7 +87,7 @@ Object.Destroy(gameObject);
var document = gameObject.AddComponent<UIDocument>(); var document = gameObject.AddComponent<UIDocument>();
try try
{ {
var panelSettings =await ModService.LoadAsset<PanelSettings>("ux_panel_settings"); var panelSettings =await ModService.LoadAsset<PanelSettings>(SettingsPath);
document.panelSettings = panelSettings; document.panelSettings = panelSettings;
} }
catch (Exception e) catch (Exception e)

View File

@ -102,7 +102,16 @@ namespace BITKit
handle.VisualElement.RemoveFromHierarchy(); handle.VisualElement.RemoveFromHierarchy();
_visibleHandle.RemoveElement(handle); _visibleHandle.RemoveElement(handle);
} }
_visibleHandle.Invoke();
try
{
_visibleHandle.Invoke();
}
catch (Exception e)
{
Debug.LogException(e);
}
} }
public override async UniTask InitializeAsync() public override async UniTask InitializeAsync()
@ -116,6 +125,8 @@ namespace BITKit
UXUtils.Inject(this); UXUtils.Inject(this);
_container.Clear(); _container.Clear();
_visibleHandle.Invoke();
_initializationState = InitializationState.Initialized; _initializationState = InitializationState.Initialized;
} }

View File

@ -714,5 +714,48 @@ namespace BITKit
self.SetOpacity(visible ? 1 : 0); self.SetOpacity(visible ? 1 : 0);
} }
public static void ClearTooltipsOnPointerLeave(this VisualElement visualElement)
{
visualElement.RegisterCallback<PointerLeaveEvent>(OnLeave);
return;
void OnLeave(PointerLeaveEvent evt)
{
if (string.IsNullOrEmpty(visualElement.tooltip) is false)
visualElement.tooltip = string.Empty;
}
}
public static void BlinkingCursor(this TextField tf,int interval=1000)
{
tf.schedule.Execute(() =>
{
if(tf.ClassListContains("transparentCursor"))
tf.RemoveFromClassList("transparentCursor");
else
tf.AddToClassList("transparentCursor");
}).Every(interval);
}
public static Vector2 GetAbsolutePositionInUI(this VisualElement element)
{
var position = Vector2.zero;
var currentElement = element;
// 遍历每一个父元素,并累计位置
while (currentElement != null)
{
var style = currentElement.resolvedStyle;
// 累加该元素相对于父元素的位置
position.x += style.left;
position.y += style.top;
// 移动到父元素
currentElement = currentElement.parent;
}
return position;
}
} }
} }

View File

@ -21,7 +21,7 @@ MonoBehaviour:
m_FallbackDpi: 96 m_FallbackDpi: 96
m_ReferenceResolution: {x: 1920, y: 1080} m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0 m_ScreenMatchMode: 0
m_Match: 0.421 m_Match: 0.5
m_SortingOrder: 0 m_SortingOrder: 0
m_TargetDisplay: 0 m_TargetDisplay: 0
m_ClearDepthStencil: 1 m_ClearDepthStencil: 1

View File

@ -93,6 +93,37 @@ TabBar Button:disabled {
margin-left: 32px; margin-left: 32px;
} }
.gap-8 {
margin-top: -8px;
margin-right: -8px;
margin-bottom: -8px;
margin-left: -8px;
}
.gap-8 > * {
margin: 8px;
}
.gap-x-8 {
margin-right: -8px;
margin-left: -8px;
}
.gap-x-8 > * {
margin-left: 8px;
margin-right: 8px;
}
.gap-y-8 {
margin-top: -8px;
margin-bottom: -8px;
}
.gap-y-8 > * {
margin-top: 8px;
margin-bottom: 8px;
}
.r-8 { .r-8 {
border-top-left-radius: 8px; border-top-left-radius: 8px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
@ -473,3 +504,7 @@ Button.clear {
border-top-color: rgba(0, 0, 0, 0); border-top-color: rgba(0, 0, 0, 0);
border-bottom-color: rgba(0, 0, 0, 0); border-bottom-color: rgba(0, 0, 0, 0);
} }
TabContainer > * {
flex-grow: 1;
}

View File

@ -1,5 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:Template name="ModTemplate" src="project://database/Assets/BITKit/Unity/UX/ModTemplate.uxml?fileID=9197481963319205126&amp;guid=5d8350eb5da74b34a81d90d7fdea10c7&amp;type=3#ModTemplate" /> <ui:Template name="ModTemplate" src="project://database/Assets/BITKit/Unity/UX/ux_mod_service_template.uxml?fileID=9197481963319205126&amp;guid=5d8350eb5da74b34a81d90d7fdea10c7&amp;type=3#ux_mod_service_template" />
<Style src="project://database/Assets/BITKit/Unity/UX/Common/Common.uss?fileID=7433441132597879392&amp;guid=a3a69d3518fd02b489e721f3c5b0b539&amp;type=3#Common" /> <Style src="project://database/Assets/BITKit/Unity/UX/Common/Common.uss?fileID=7433441132597879392&amp;guid=a3a69d3518fd02b489e721f3c5b0b539&amp;type=3#Common" />
<Style src="project://database/Assets/BITKit/Unity/UX/UXModService.uss?fileID=7433441132597879392&amp;guid=4603eeaf39bae3448a93680711a15c42&amp;type=3#UXModService" /> <Style src="project://database/Assets/BITKit/Unity/UX/UXModService.uss?fileID=7433441132597879392&amp;guid=4603eeaf39bae3448a93680711a15c42&amp;type=3#UXModService" />
<Style src="project://database/Assets/BITKit/Unity/UX/BITAlert.uss?fileID=7433441132597879392&amp;guid=8d0db0fee932f5342988f09217d6309a&amp;type=3#BITAlert" /> <Style src="project://database/Assets/BITKit/Unity/UX/BITAlert.uss?fileID=7433441132597879392&amp;guid=8d0db0fee932f5342988f09217d6309a&amp;type=3#BITAlert" />
@ -31,12 +31,13 @@
<ui:VisualElement style="width: 384px; margin-left: 64px;"> <ui:VisualElement style="width: 384px; margin-left: 64px;">
<ui:Label tabindex="-1" text="Mod列表" parse-escape-sequences="true" display-tooltip-when-elided="true" class="tl" /> <ui:Label tabindex="-1" text="Mod列表" parse-escape-sequences="true" display-tooltip-when-elided="true" class="tl" />
<ui:VisualElement name="info-container" style="flex-grow: 1;"> <ui:VisualElement name="info-container" style="flex-grow: 1;">
<ui:VisualElement name="mod-image" style="height: 256px; background-color: rgba(0, 0, 0, 0); background-image: url(&apos;project://database/Assets/BITKit/Unity/Art/Images/Mod_Package.png?fileID=2800000&amp;guid=7ef0888883a3a7442989365c2dc57862&amp;type=3#Mod_Package&apos;); -unity-background-scale-mode: scale-and-crop;" /> <ui:VisualElement name="mod-image" style="height: 256px; background-color: rgba(0, 0, 0, 0); background-image: url(&quot;project://database/Assets/BITKit/Unity/Art/Images/Mod_Package.png?fileID=2800000&amp;guid=7ef0888883a3a7442989365c2dc57862&amp;type=3#Mod_Package&quot;); -unity-background-scale-mode: scale-and-crop;" />
<ui:VisualElement style="padding-top: 8px; padding-right: 0; padding-bottom: 8px; padding-left: 0; flex-grow: 1;"> <ui:VisualElement style="padding-top: 8px; padding-right: 0; padding-bottom: 8px; padding-left: 0; flex-grow: 1;">
<ui:Label tabindex="-1" text="加载一个Mod" parse-escape-sequences="true" display-tooltip-when-elided="true" name="mod-name-label" class="tl" /> <ui:Label tabindex="-1" text="加载一个Mod" parse-escape-sequences="true" display-tooltip-when-elided="true" name="mod-name-label" class="tl" />
<ui:Label tabindex="-1" text="通过Windows Explorer选择Mod后,需要等待一段时间才能加载,这是因为explorer内部的延迟&#10;&#10;可以直接加载编译好的dll&#10;加载.cs需要安装MonoBleedingEdge&#10;&#10;已加载的脚本mod无法动态完成卸载,可能需要重启应用&#10;&#10;动态加载的Mod不会保存,如需要每次启动都自动加载,需要将Mod放置在/Mods/{ModName}/文件夹中,并创建PackageInfo.json描述文件" parse-escape-sequences="true" display-tooltip-when-elided="true" name="mod-description-label" style="white-space: normal;" /> <ui:Label tabindex="-1" text="通过Windows Explorer选择Mod后,需要等待一段时间才能加载,这是因为explorer内部的延迟&#10;&#10;可以直接加载编译好的dll&#10;加载.cs需要安装MonoBleedingEdge&#10;&#10;已加载的脚本mod无法动态完成卸载,可能需要重启应用&#10;&#10;动态加载的Mod不会保存,如需要每次启动都自动加载,需要将Mod放置在/Mods/{ModName}/文件夹中,并创建PackageInfo.json描述文件" parse-escape-sequences="true" display-tooltip-when-elided="true" name="mod-description-label" style="white-space: normal;" />
<ui:VisualElement style="flex-grow: 1;" /> <ui:VisualElement style="flex-grow: 1;" />
<ui:Button text="打开开发手册" parse-escape-sequences="true" display-tooltip-when-elided="true" name="open-manual-button" /> <ui:Button text="打开开发手册" parse-escape-sequences="true" display-tooltip-when-elided="true" name="open-manual-button" />
<ui:Button text="打开Mod目录" parse-escape-sequences="true" display-tooltip-when-elided="true" name="open_folder-button" />
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>
</ui:VisualElement> </ui:VisualElement>

View File

@ -0,0 +1,69 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.TextCore.Text;
using UnityEngine.UIElements;
namespace BITKit.GameEditor
{
public class TMPFontCheck : EditorWindow
{
public static FontAsset CurrentFont;
[MenuItem("Tools/TextMeshPro Font Check")]
public static void ShowExample()
{
GetWindow<TMPFontCheck>().Show();
}
private void CreateGUI()
{
rootVisualElement.Clear();
var refreshButton = rootVisualElement.Create<Button>();
refreshButton.text = "刷新";
refreshButton.clicked += CreateGUI;
var mergeButton = rootVisualElement.Create<Button>();
mergeButton.text = "合并字符串";
var fontField = rootVisualElement.Create<ObjectField>();
fontField.objectType = typeof(FontAsset);
fontField.RegisterValueChangedCallback(x =>
{
CurrentFont = x.newValue as FontAsset;
});
fontField.SetValueWithoutNotify(CurrentFont);
var container = rootVisualElement.Create<VisualElement>();
container.style.flexDirection = FlexDirection.Column;
var label = container.Create<Label>();
label.style.unityFontDefinition = new FontDefinition()
{
fontAsset = CurrentFont
};
var textfield = container.Create<TextField>();
textfield.label = "Test Font";
textfield.multiline = true;
textfield.RegisterValueChangedCallback(x =>
{
label.text = x.newValue;
});
textfield.style.unityFontDefinition = label.style.unityFontDefinition;
mergeButton.clicked += () =>
{
textfield.value =new string(Regex.Replace(textfield.value, @"\s+", "").Distinct().ToArray());
};
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 707aad4d7f4d82c46866eb25231c2388
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: