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
{
[Obsolete("Use InitializeAsync instead")]
public static UniTask Init(string name)=>UniTask.CompletedTask;
private static readonly Timer _timer = new();
private static Timer _timer = new();
private static DateTime _startTime = DateTime.UtcNow;
private static readonly DateTime _startTime = DateTime.UtcNow;
public static async UniTask InitializeAsync(string name)
{
BIT4Log.OnLog += Console.WriteLine;
BIT4Log.OnWarning += Console.WriteLine;
BIT4Log.OnException += e => Console.WriteLine(e.ToString());
BIT4Log.OnSetConsoleColor += color => Console.ForegroundColor = color;
BIT4Log.OnNextLine += Console.WriteLine;

View File

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

View File

@ -126,7 +126,12 @@ namespace BITKit
}
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)
{
await BITApp.SwitchToMainThread();
if (string.IsNullOrEmpty(path))
{
throw new TimeoutException("请求超时或已断开连接");
@ -561,7 +560,6 @@ namespace BITKit.Net
}
throw new TimeoutException($"请求超时或已断开连接,请求为{path}");
}
if (_p2p.TryRemove(id, out var value))
{

View File

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

View File

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

View File

@ -103,10 +103,11 @@ namespace BITKit
if (IsSyncContext)
{
await BITApp.SwitchToMainThread();
#if UNITY_EDITOR
await BITApp.SwitchToMainThread();
if (UnityEditor.EditorApplication.isPaused)
{
_timer.Interval = 1000d / TickRate;
_timer.Start();
return;
}
@ -164,12 +165,19 @@ namespace BITKit
TickCount++;
if(_isDisposed)return;
_timer.Interval = 1000d / TickRate;
_timer.Start();
}
public bool IsSyncContext { get; set; } = true;
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 event Func<float, UniTask> OnTickAsync;

View File

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

View File

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

View File

@ -228,9 +228,24 @@ namespace BITKit.Console
_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)
{
case KeyCode.Return:
@ -240,35 +255,30 @@ namespace BITKit.Console
_textField.SetValueWithoutNotify(string.Empty);
await UniTask.NextFrame();
_textField.Blur();
_textField.Focus();
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;
break;
default:
nextStop = false;
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
break;
}
if (nextStop)
{
keyDownEvent.StopPropagation();
keyDownEvent.PreventDefault();
}
}
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>
{
[HideInCallstack]
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
switch (logLevel)
@ -56,7 +57,7 @@ namespace BITKit
}
break;
default:
Debug.Log($"<color=cyan>{typeof(T).Name}</color>:{state.ToString()}");
Debug.Log($"<b>{typeof(T).Name}</b>:{state.ToString()}");
break;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -21,16 +21,10 @@ namespace BITKit.UX
private const string TemplatePath = "ux_mod_service_template";
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;
[UXBindPath("open_folder-button")]
private Button _openFolderButton;
[UXBindPath("open-mod-button")]
private Button _openModButton;
[UXBindPath("mods-container")]
@ -43,22 +37,27 @@ namespace BITKit.UX
private Label _modDescriptionLabel;
[UXBindPath("reload-mod-button",true)]
private Button _reloadModButton;
[UXBindPath("install-roslyn-fill")]
private VisualElement _installRoslynFill;
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)
{
RootVisualElement?.SetEnabled(!obj);
OnInitiatedAsync += InitializeAsync;
}
public override async UniTask EntryAsync()
private async UniTask InitializeAsync()
{
await base.EntryAsync();
_installRoslynFill.style.width = 0;
_modTemplate =await ModService.LoadAsset<VisualTreeAsset>(TemplatePath);
UXUtils.Inject(this);
if (_openModButton is not null)
{
_openModButton.clicked += OpenMod;
}
if (_returnButton is not null)
{
@ -79,7 +78,22 @@ namespace BITKit.UX
await UniTask.SwitchToMainThread();
_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)
{
await UniTask.SwitchToMainThread();
@ -117,78 +131,6 @@ namespace BITKit.UX
var container = _modContainers.GetOrAdd(obj.Name,_=> Create(obj));
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)
{
var container =_modsContainer.Create(_modTemplate);
@ -219,7 +161,7 @@ namespace BITKit.UX
ModService.OnModUnInstalled-=OnModUnInstalled;
ModService.OnModLoaded-=OnModLoaded;
ModService.OnModUnLoaded-=OnModUnLoaded;
ModService.OnLocked-=OnLocked;
ModService.IsBusy.RemoveListener(OnLocked);
}
}

View File

@ -18,7 +18,7 @@ using Debug = UnityEngine.Debug;
namespace BITKit.UX
{
public abstract class UIToolKitPanel : IUXPanel
public abstract class UIToolKitPanel : IUXPanel,IDisposable
{
public const string USSEntry = "transition_entry";
public const string USSEntryAsync = "transition_entry_async";
@ -128,6 +128,7 @@ namespace BITKit.UX
public virtual bool AllowCursor { get; }
public virtual bool AllowInput { get; }
public virtual string[] InitialUssClasses { get; } = Array.Empty<string>();
public bool IsDisposed { get; private set; }
public bool IsEntered { get; set; }
protected virtual void OnReturn()
@ -247,6 +248,12 @@ namespace BITKit.UX
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 VisualElement RootVisualElement { get; set; }
protected bool IsInitialized { get; private set; }
protected readonly UniTaskCompletionSource WaitUntilInitialized = new();
protected UIToolkitOverlay(IUXService uxService, CancellationTokenSource cancellationToken)
{
UXService = uxService;
@ -40,6 +41,8 @@ namespace BITKit.UX
UXUtils.Inject(this);
IsInitialized = true;
WaitUntilInitialized.TrySetResult();
}
public virtual void Dispose()
{

View File

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

View File

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

View File

@ -714,5 +714,48 @@ namespace BITKit
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_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_Match: 0.421
m_Match: 0.5
m_SortingOrder: 0
m_TargetDisplay: 0
m_ClearDepthStencil: 1

View File

@ -93,6 +93,37 @@ TabBar Button:disabled {
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 {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
@ -473,3 +504,7 @@ Button.clear {
border-top-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: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/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" />
@ -31,12 +31,13 @@
<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: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: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:VisualElement style="flex-grow: 1;" />
<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>

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: