This commit is contained in:
CortexCore
2025-07-11 11:45:45 +08:00
parent fc189b98cc
commit ecae0f809c
76 changed files with 237471 additions and 33136 deletions

View File

@@ -13,7 +13,7 @@ namespace BITKit.Physics
public string Name="Default";
public readonly Transform Transform;
public Vector3 Offset = default;
public LayerMask LayerMask=LayerMask.NameToLayer("Default");
public LayerMask LayerMask=LayerMask.GetMask("Default");
public float Distance=1.6f;
public float MinHeight=0.32f;
public float MaxHeight = 1f;

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5e0aa9ddf175a2248af24307391f860f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
{
"name": "Net.BITKit.SignalR.Unity",
"rootNamespace": "",
"references": [
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [
"Disabled"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 032072f82da34ae4895a3c477957e594
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
using System;
using Microsoft.AspNetCore.SignalR.Client;
using UnityEngine;
using System.Threading.Tasks;
using BITKit;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.Logging;
public class SignalRClient : MonoBehaviour
{
[SerializeField] private string url = @"https://yourserver.com/chathub";
private HubConnection _connection;
private ILogger<SignalRClient> _logger;
private StopWatcher _stopWatcher;
// 用于与 SignalR 服务器进行通信
private async void Start()
{
await UniTask.Delay(300);
if(destroyCancellationToken.IsCancellationRequested)return;
_connection = new HubConnectionBuilder()
.WithUrl(url)
.Build();
BITApp.ServiceProvider.QueryComponents(out _logger);
_connection.On<string, string>("ReceiveMessage", (user, message) =>
{
_stopWatcher?.Dispose();
_logger.LogInformation($"Received from Blazor/Server: {user} - {message}");
});
destroyCancellationToken.Register(Dispose);
await _connection.StartAsync();
_logger.LogInformation("Unity connected to SignalR server!");
// 发送消息给 Blazor
await SendMessageToBlazor("Unity", "Hello from Unity!");
for (var i = 0; i < 16; i++)
{
_stopWatcher = new StopWatcher(_logger, $"SignalR延迟 at {i}");
await SendMessageToBlazor("Unity", "OK,Let's Count!");
}
var id = await _connection.InvokeAsync<string>("GetMessage");
_logger.LogInformation($"获取用户ID:{id}");
}
private async void Dispose()
{
await _connection.StopAsync();
}
private async Task SendMessageToBlazor(string user, string message)
{
await _connection.InvokeAsync("SendMessage", user, message);
}
}

View File

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

View File

@@ -20,7 +20,8 @@
"GUID:a11ff146d38b27a44af87b4b4d9c4ecb",
"GUID:e4d11af1289097a4d9d8987f332a2ae8",
"GUID:3abaaefa7af558d44ba20cea1c43d602",
"GUID:d8b63aba1907145bea998dd612889d6b"
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:a3cd7886d0941284aa4f5e020270c73a"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -19,6 +19,11 @@ namespace BITKit.UX
{
name = "ReleasePress"
};
private readonly UxmlStringAttributeDescription m_TextAttribute = new()
{
name = "Text"
};
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
@@ -26,12 +31,19 @@ namespace BITKit.UX
var table = (OnScreenButton)ve;
table.PressedValue = m_PressedValueAttribute.GetValueFromBag(bag, cc);
table.ReleasePress = m_ReleasePressAttribute.GetValueFromBag(bag, cc);
table.Text = m_TextAttribute.GetValueFromBag(bag, cc);
}
}
public new class UxmlFactory : UxmlFactory<OnScreenButton, UxmlTraits> { }
public float PressedValue { get; set; }= 1f;
public bool ReleasePress { get; set; }
public string Text
{
get => _label.text;
set => _label.text = value;
}
private bool _isPressed;
private int _pointerId=-1;
private Label _label;
@@ -44,6 +56,7 @@ namespace BITKit.UX
RegisterCallback<PointerMoveEvent>(OnPointerMove);
this.AddManipulator(new Clickable(OnClick));
_label = this.Create<Label>();
_label.pickingMode = PickingMode.Ignore;
_isTriggered.AddListener(x =>
{

View File

@@ -64,10 +64,18 @@ namespace BITKit.UX
button.RegisterCallback<PointerOverEvent>(_ =>
{
SendValueToControl((Vector2)value);
button.AddToClassList("selected");
});
button.RegisterCallback<PointerUpEvent>(_ =>
{
SendValueToControl((Vector2)default);
button.RemoveFromClassList("selected");
});
button.RegisterCallback<PointerCancelEvent>(_ =>
{
button.RemoveFromClassList("selected");
});
// var label = button.Create<Label>();

View File

@@ -45,12 +45,16 @@ namespace BITKit.UX
SendValueToControl(Vector2.zero);
_startPosition = evt.position;
_ignoreFrame = 1;
this.ReleasePointer(evt.pointerId);
}
private void OnPointerDown(PointerDownEvent evt)
{
_ignoreFrame = 1;
_startPosition = evt.position;
this.CapturePointer(evt.pointerId);
}
private void OnPointerMove(PointerMoveEvent evt)

View File

@@ -81,7 +81,10 @@ namespace BITKit.UX
else
{
var label = instance.Create<Label>();
label.text = array[x].ToString();
var text = array[x].ToString().Replace(@"\r\n", @"\n").Replace(@"\r", @"\n");
label.text = text;
}
}

View File

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

View File

@@ -1,78 +1,207 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using BITKit;
using BITKit.UX;
using Cysharp.Threading.Tasks;
using Net.BITKit.Localization;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
namespace Net.BITKit.UX
{
public class UXLocalization
public class UXLocalization:IDisposable
{
public string USS { get; set; } = "localized";
public string[] Lang { get; set; } =
{
"zh-CN",
"en-US",
"ja-JP",
};
public static string USS { get; set; } = "localized";
private readonly IUXService _uxService;
private readonly ILocalizationService _localizationService;
private readonly IMainTicker _ticker;
public UXLocalization(IUXService uxService, ILocalizationService localizationService)
public UXLocalization(IUXService uxService, ILocalizationService localizationService, IMainTicker ticker)
{
_uxService = uxService;
_localizationService = localizationService;
_ticker = ticker;
_localizationService.OnLanguageChanged += OnLanguageChanged;
_localizationService.OnLocalizeAsync += OnLocalizeAsync;
_ticker.Add(OnTick);
}
private void OnTick(float obj)
{
if (Mouse.current.rightButton.wasPressedThisFrame is false) return;
if (_uxService.TryPick(Mouse.current.position.ReadValue(), out var o) is false) return;
if (o is not VisualElement visualElement) return;
if (visualElement.ClassListContains(USS) is false) return;
var builder =
ContextMenuBuilder
.Create()
.BuildText(_localizationService.GetLocalizedString("Translate"));
foreach (var lang in Lang)
{
builder.BuildAction(lang, () => Localize(visualElement, lang));
}
/*
foreach (var lang in Enum.GetValues(typeof(Lang)))
{
var code = lang.GetType().GetMember(lang.ToString())[0].GetCustomAttribute<DescriptionAttribute>()
.Description;
builder.BuildAction(code, () => Localize(visualElement, code));
}
*/
builder.BuildAction("Default", () => Localize(visualElement, _localizationService.CurrentLanguage));
builder.Build();
}
private UniTask<bool> OnLocalizeAsync(string arg1, object arg2)
{
if (arg2 is not VisualElement visualElement) return UniTask.FromResult(false);
Localize((object)visualElement);
return UniTask.FromResult(true);
}
private void Localize(VisualElement visualElement, string lang = null)
{
if (string.IsNullOrEmpty(lang))
{
lang = _localizationService.CurrentLanguage;
}
var currentLang = _localizationService.GetLocalizedString('#' + visualElement.viewDataKey,
_localizationService.CurrentLanguage);
var translate = _localizationService.GetLocalizedString('#' + visualElement.viewDataKey, lang);
if (string.Equals(visualElement.tooltip, currentLang))
{
visualElement.tooltip = translate;
foreach (var child in visualElement.Children())
{
if (child.pickingMode is PickingMode.Position)
{
child.tooltip = translate;
}
}
}
switch (visualElement)
{
case Label label:
{
label.text = translate;
}
break;
case Button button:
{
button.text = translate;
}
break;
case ProgressBar progressBar:
{
progressBar.title = translate;
}
break;
case Foldout foldout:
{
foldout.text = translate;
}
break;
default:
{
ContinueWithBaseType(visualElement.GetType());
void ContinueWithBaseType(Type type)
{
while (true)
{
if (type is not null)
{
if (type.GetProperty("label", ReflectionHelper.Flags) is { } propertyInfo)
{
//Debug.Log($"{x.name}:#{x.viewDataKey}");
propertyInfo.SetValue(visualElement,translate);
}
else
{
type = type.BaseType;
continue;
}
}
if (type == null || type == typeof(object))
{
break;
//throw new NotImplementedException($"{visualElement.GetType().Name} is not translatable");
}
}
}
}
break;
}
}
private void Localize(object obj)
{
switch (obj)
{
case VisualElement visualElement:
{
foreach (var x in visualElement.Query(className:USS).Build())
{
Localize(x);
}
/*
foreach (var x in visualElement.Query<Label>(className:USS).ToList())
{
if (string.IsNullOrEmpty(x.viewDataKey))continue;
x.text = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query<Button>(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
x.text = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query<ProgressBar>(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
x.title = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
continue;
}
*/
}
break;
}
}
private void OnLanguageChanged(string arg1, string arg2)
{
if(_uxService.Root is not VisualElement visualElement)return;
if (_uxService.Root is not VisualElement visualElement) return;
foreach (var x in visualElement.Query<Label>(className:USS).ToList())
{
if (string.IsNullOrEmpty(x.viewDataKey))continue;
x.text = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
Localize(visualElement);
}
foreach (var x in visualElement.Query<Button>(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
x.text = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query<ProgressBar>(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
x.title = _localizationService.GetLocalizedString('#'+x.viewDataKey);
}
foreach (var x in visualElement.Query(className:USS).ToList())
{
if(string.IsNullOrEmpty(x.viewDataKey))continue;
ContinueWithBaseType(x.GetType());
continue;
void ContinueWithBaseType(Type type)
{
while (true)
{
if (type == null || type == typeof(object)) return;
if (type.GetProperty("label", ReflectionHelper.Flags) is { } propertyInfo)
{
//Debug.Log($"{x.name}:#{x.viewDataKey}");
propertyInfo.SetValue(x, _localizationService.GetLocalizedString('#' + x.viewDataKey));
}
else
{
type = type.BaseType;
continue;
}
break;
}
}
}
public void Dispose()
{
_ticker.Remove(OnTick);
}
}

View File

@@ -10,6 +10,7 @@ namespace BITKit.UX
{
public class UXRadialMenu : UIToolKitPanel,IUXHotKey
{
private static UXRadialMenu _singleton;
protected override string DocumentPath => "ui_radial_menu";
public override bool CloseWhenClickOutside => true;
public override bool AllowCursor => true;
@@ -27,6 +28,13 @@ namespace BITKit.UX
public UXRadialMenu(IUXService uxService) : base(uxService)
{
OnInitiatedAsync += InitiatedAsync;
if (_singleton is not null)
{
throw new InvalidOperationException($"{nameof(UXRadialMenu)} only can be one singleton");
}
_singleton = this;
}
private async UniTask InitiatedAsync()
@@ -131,6 +139,13 @@ var button = container.Get<Button>();
}
UXService.Entry(this);
}
public override void Dispose()
{
base.Dispose();
_singleton = null;
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Threading;
using BITKit.Mod;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using Net.BITKit.Localization;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.InputSystem;
@@ -107,10 +108,17 @@ namespace BITKit.UX
RootVisualElement.RegisterCallback<TransitionEndEvent>(OnTransitionEnd);
RootVisualElement.RegisterCallback<TransitionCancelEvent>(OnTransitionEnd);
if (BITApp.ServiceProvider.QueryComponents(out ILocalizationService localizationService))
{
await localizationService.LocalizeAsync(RootVisualElement);
}
WaitUtilTransitionCompleted.TrySetResult();
WaitUtilInitialized.TrySetResult();
OnInitiated?.Invoke();
}
}
@@ -206,6 +214,8 @@ namespace BITKit.UX
RootVisualElement.RemoveFromClassList(USSEntry);
RootVisualElement.RemoveFromClassList(USSEntryAsync);
IsEntered = true;
}
private void OnTransitionEnd<TChangeEvent>(TChangeEvent evt)
{
@@ -219,6 +229,8 @@ namespace BITKit.UX
public override void OnStateExit(IState old, IState newState)
{
IsEntered = false;
OnPanelExit();
InputActionGroup.allowInput.RemoveElement(this);
OnExit?.Invoke();

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using BITKit.StateMachine;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Net.BITKit.Localization;
using UnityEngine;
using UnityEngine.UIElements;
@@ -11,16 +12,13 @@ namespace BITKit.UX
{
public class UIToolkitSubPanel<T> :IUXPanel where T : IUXPanel
{
private readonly IServiceProvider _serviceProvider;
protected readonly T Panel;
protected readonly ValidHandle IsVisible=new();
private readonly ValidHandle _busy = new();
public UIToolkitSubPanel(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
protected UIToolkitSubPanel(IServiceProvider serviceProvider)
{
Panel = serviceProvider.GetRequiredService<T>();
Panel.OnInitiated += OnInitiated;

View File

@@ -72,7 +72,7 @@ namespace BITKit.UX.Settings
slider.label = _localizationService.GetLocalizedString(name);
slider.showInputField = true;
slider.showMixedValue = true;
slider.lowValue = 1;
slider.lowValue = 0.01f;
slider.highValue = 100;
slider.SetValueWithoutNotify(value.As<float>());
slider.RegisterValueChangedCallback(x =>

View File

@@ -10,16 +10,27 @@ using UnityEngine.UIElements;
namespace Net.BITKit.UX.SnackBar
{
public class UXSnackBar<TPanel> : UIToolkitSubPanel<TPanel>,ISnackBar where TPanel :IUXPanel
public static class UXSnackBarStatic
{
public static int Count;
}
public class UXSnackBar<TPanel> : UIToolkitSubPanel<TPanel>,IDisposable,ISnackBar where TPanel :IUXPanel
{
[UXBindPath("snack_bar-container")] private VisualElement _snackBarContainer;
private VisualTreeAsset _template;
private readonly ValidHandle _waitHandle = new ValidHandle();
private readonly Queue<(string message,Severity severity)> _queue=new();
public UXSnackBar(IServiceProvider serviceProvider) : base(serviceProvider)
{
if (UXSnackBarStatic.Count > 0)
{
throw new Exception("SnackBar can only be created once");
}
UXSnackBarStatic.Count++;
}
protected override void OnInitiated()
@@ -30,6 +41,11 @@ namespace Net.BITKit.UX.SnackBar
_snackBarContainer.Clear();
BITAppForUnity.AllowCursor.AddListener(OnAllowCursor);
while (_queue.TryDequeue(out var temp ))
{
Add(temp.message,temp.severity);
}
}
private void OnAllowCursor(bool obj)
@@ -44,26 +60,43 @@ namespace Net.BITKit.UX.SnackBar
}
}
public async void Add(string message, Severity severity = Severity.Normal)
public void Add(string message, Severity severity = Severity.Normal)
{
if (_snackBarContainer is null)
{
_queue.Enqueue(new ValueTuple<string, Severity>(message,severity));
return;
}
var ve = _snackBarContainer.Create(_template);
ve.AddToClassList($"severity-{severity.ToString().ToLower()}");
ve.Get<Label>().text = message;
if (ve.Q("VisualElement--1") is { } bar)
{
await BITween.Lerp(x => bar.style.width = new StyleLength(Length.Percent(x)), 0f, 100, 5, Mathf.Lerp);
}
else
{
await UniTask.Delay(5000);
}
Continue();
await _waitHandle;
return;
async void Continue()
{
if (ve.Q("VisualElement--1") is { } bar)
{
await BITween.Lerp(x => bar.style.width = new StyleLength(Length.Percent(x)), 0f, 100, 5, Mathf.Lerp);
}
else
{
await UniTask.Delay(5000);
}
await _waitHandle;
ve.RemoveFromHierarchy();
ve.RemoveFromHierarchy();
}
}
public void Dispose()
{
UXSnackBarStatic.Count--;
}
}

View File

@@ -48,6 +48,9 @@ namespace BITKit.UX
Hovering = null;
return;
}
if(_rootVisualElement is not {panel:not null})return;
var tooltip = CurrentToolTip(_rootVisualElement.panel);
if (tooltip != "")
{

View File

@@ -71,10 +71,9 @@ namespace BITKit.UX
return;
}
}
foreach (var fieldInfo in self.GetType()
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)
foreach (var fieldInfo in BITSharp.GetAllBaseType(self.GetType()).SelectMany(x=>x.GetFields(ReflectionHelper.Flags))
.Where(x => x.GetCustomAttribute<UXBindPathAttribute>() is not null)
)
{
@@ -100,9 +99,10 @@ namespace BITKit.UX
}
catch (Exception e)
{
BIT4Log.Warning<UXUtils>(fieldInfo!.Name);
BIT4Log.Warning<UXUtils>($"{fieldInfo!.Name}:{e.Message}");
}
}
}
}
}

View File

@@ -668,6 +668,7 @@ namespace BITKit
{
self.style.display = new(active ? DisplayStyle.Flex : DisplayStyle.None);
}
public static void SetVisible(this VisualElement self, bool visible)
{
self.style.visibility = new(visible ? Visibility.Visible : Visibility.Hidden);