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

@@ -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>