From d502501b27fa9784bba9214fb447154818d0dbd3 Mon Sep 17 00:00:00 2001 From: CortexCore <2630229280@qq.com> Date: Fri, 13 Dec 2024 16:14:20 +0800 Subject: [PATCH] Added UI Toolkit Progress --- .../Extensions/DisplayAttributeExtensions.cs | 55 ++++ Src/Core/Extensions/HttpClient.cs | 4 + Src/Core/Ticker/ITicker.cs | 116 +++++++- Src/Core/Utility/StopWatcher.cs | 2 +- Src/Unity/Scripts/Assets/BITFramework.cs | 45 ++- .../UX/Chart/BITKit.UX.Chart.Runtime.asmdef | 6 +- Src/Unity/Scripts/UX/Chart/SkiaChart.cs | 265 ++++++++++-------- .../UX/Library/ComplexRadialProgress.cs | 169 +++++++++++ .../UX/Library/ComplexRadialProgress.cs.meta | 11 + .../Scripts/UX/Library/DashBoardProgress.cs | 101 +++++++ .../UX/Library/DashBoardProgress.cs.meta | 11 + Src/Unity/Scripts/UX/Library/TabContainer.cs | 4 + Src/Unity/Scripts/UX/Utils/UXUtils.cs | 2 +- .../UX/{BITConsole.uxml => ui_console.uxml} | 0 ...Console.uxml.meta => ui_console.uxml.meta} | 0 ...TContextMenu.uxml => ui_context_menu.uxml} | 0 ...nu.uxml.meta => ui_context_menu.uxml.meta} | 0 .../UX/{UXToolTips.uxml => ui_tooltips.uxml} | 0 ...olTips.uxml.meta => ui_tooltips.uxml.meta} | 0 .../BITKit.Extensions.SkiaSharp.asmdef | 3 +- 20 files changed, 660 insertions(+), 134 deletions(-) create mode 100644 Src/Core/Extensions/DisplayAttributeExtensions.cs create mode 100644 Src/Unity/Scripts/UX/Library/ComplexRadialProgress.cs create mode 100644 Src/Unity/Scripts/UX/Library/ComplexRadialProgress.cs.meta create mode 100644 Src/Unity/Scripts/UX/Library/DashBoardProgress.cs create mode 100644 Src/Unity/Scripts/UX/Library/DashBoardProgress.cs.meta rename Src/Unity/UX/{BITConsole.uxml => ui_console.uxml} (100%) rename Src/Unity/UX/{BITConsole.uxml.meta => ui_console.uxml.meta} (100%) rename Src/Unity/UX/{BITContextMenu.uxml => ui_context_menu.uxml} (100%) rename Src/Unity/UX/{BITContextMenu.uxml.meta => ui_context_menu.uxml.meta} (100%) rename Src/Unity/UX/{UXToolTips.uxml => ui_tooltips.uxml} (100%) rename Src/Unity/UX/{UXToolTips.uxml.meta => ui_tooltips.uxml.meta} (100%) diff --git a/Src/Core/Extensions/DisplayAttributeExtensions.cs b/Src/Core/Extensions/DisplayAttributeExtensions.cs new file mode 100644 index 0000000..06b63f5 --- /dev/null +++ b/Src/Core/Extensions/DisplayAttributeExtensions.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Reflection; + +namespace BITKit +{ + public static class DisplayAttributeExtensions + { + public static string GetDisplayName(this PropertyInfo self) + { + if (self.GetCustomAttribute() is not { } displayNameAttribute) return self.Name; + var name = displayNameAttribute.DisplayName; + return string.IsNullOrEmpty(name) ? self.Name : name; + } + public static string GetDisplayName(this FieldInfo self) + { + if (self.GetCustomAttribute() is not { } displayNameAttribute) return self.Name; + var name = displayNameAttribute.DisplayName; + return string.IsNullOrEmpty(name) ? self.Name : name; + } + public static string GetDisplayName(this object self) + { + { + if (self.GetType().GetCustomAttribute() is { } displayNameAttribute) + { + return displayNameAttribute.DisplayName; + } + + if (self.GetType().GetCustomAttribute() is { } displayAttribute) + { + return displayAttribute.Name; + } + } + { + if (self is Enum) + { + var field = self.GetType().GetField(self.ToString()!); + + if (field.GetCustomAttribute() is DisplayNameAttribute displayNameAttribute) + { + return displayNameAttribute.DisplayName; + } + + if (field.GetCustomAttribute() is DisplayAttribute displayAttribute) + { + return displayAttribute.Name; + } + } + } + return self.ToString(); + } + } +} \ No newline at end of file diff --git a/Src/Core/Extensions/HttpClient.cs b/Src/Core/Extensions/HttpClient.cs index 26128cb..0d2957d 100644 --- a/Src/Core/Extensions/HttpClient.cs +++ b/Src/Core/Extensions/HttpClient.cs @@ -11,6 +11,10 @@ namespace BITKit public static async UniTask PostJsonAsync(this HttpClient self, string requestUrl, object value , CancellationToken cancellationToken=default) { var json = JsonConvert.SerializeObject(value); + if (value is string j) + { + json = j; + } var content = new StringContent(json, Encoding.UTF8, "application/json"); var response =await self.PostAsync(requestUrl, content, cancellationToken); return await response.Content.ReadAsStringAsync(); diff --git a/Src/Core/Ticker/ITicker.cs b/Src/Core/Ticker/ITicker.cs index cd21550..2fa972a 100644 --- a/Src/Core/Ticker/ITicker.cs +++ b/Src/Core/Ticker/ITicker.cs @@ -1,5 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Timers; +using Cysharp.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace BITKit { @@ -34,6 +39,16 @@ namespace BITKit void ManualTick(float delta); } /// + /// 异步循环 + /// + public interface IAsyncTicker + { + ulong TickCount { get; } + int TickRate { get; set; } + bool IsConcurrent { get; set; } + event Func OnTickAsync; + } + /// /// 主线程循环 /// public interface IMainTicker : ITicker { } @@ -53,7 +68,106 @@ namespace BITKit /// public interface IFixedTicker : ITicker{} #endif - + public class AsyncTicker : IAsyncTicker,IDisposable + { + private readonly ILogger _logger; + private readonly Timer _timer=new(); + private bool _isDisposed; + private readonly Stopwatch _stopwatch=new(); + + public AsyncTicker(ILogger logger) + { + _logger = logger; + if (TickRate <= 0) + { + TickRate = 1; + } + + _timer.Elapsed += OnElapsed; + + if (_isDisposed is false) + _timer.Start(); + + _logger.LogInformation($"异步循环就绪,TickRate:{TickRate}"); + } + #if UNITY_5_3_OR_NEWER + [UnityEngine.HideInCallstack] +#endif + private async void OnElapsed(object sender, ElapsedEventArgs e) + { + try + { + if(_isDisposed)return; + + if (IsSyncContext) + { + await BITApp.SwitchToMainThread(); + if(_isDisposed)return; + } + + var deltaTime = 1f / TickRate; + if (_stopwatch.IsRunning) + { + deltaTime = (float)_stopwatch.Elapsed.TotalSeconds; + _stopwatch.Reset(); + } + + _stopwatch.Start(); + + if (IsConcurrent) + { + var tasks = OnTickAsync.CastAsFunc().ToArray(); + + foreach (var func in tasks) + { + if (_isDisposed) return; + + try + { + await func.Invoke(deltaTime); + } + catch (Exception exception) + { + _logger.LogCritical(exception,exception.Message); + } + + } + } + else + { + try + { + await OnTickAsync.UniTaskFunc(deltaTime); + } + catch (Exception exception) + { + _logger.LogCritical(exception,exception.Message); + } + } + + + } + catch (Exception exception) + { + _logger.LogCritical(exception,exception.Message); + } + TickCount++; + + if(_isDisposed)return; + _timer.Start(); + } + + public bool IsSyncContext { get; set; } = true; + public ulong TickCount { get; set; } + public int TickRate { get; set; } + public bool IsConcurrent { get; set; } + public event Func OnTickAsync; + + public void Dispose() + { + + } + } public class Ticker : ITicker { diff --git a/Src/Core/Utility/StopWatcher.cs b/Src/Core/Utility/StopWatcher.cs index 970635b..75b46c3 100644 --- a/Src/Core/Utility/StopWatcher.cs +++ b/Src/Core/Utility/StopWatcher.cs @@ -18,7 +18,7 @@ namespace BITKit public void Dispose() { _stopwatch.Stop(); - _logger.LogInformation($"已完成:{_message},耗时{_stopwatch.Elapsed}"); + _logger.LogInformation($"已完成:{_message},耗时{_stopwatch.ElapsedMilliseconds}ms"); } } } \ No newline at end of file diff --git a/Src/Unity/Scripts/Assets/BITFramework.cs b/Src/Unity/Scripts/Assets/BITFramework.cs index e744a9b..92316ae 100644 --- a/Src/Unity/Scripts/Assets/BITFramework.cs +++ b/Src/Unity/Scripts/Assets/BITFramework.cs @@ -95,7 +95,10 @@ namespace BITKit stopwatch.Start(); DontDestroyOnLoad(gameObject); // 初始化资源系统 - YooAssets.Initialize(); + if (YooAssets.Initialized is false) + YooAssets.Initialize(); + + // 创建默认的资源包 var package = YooAssets.TryGetPackage(packageName.Value) ?? YooAssets.CreatePackage(packageName.Value); @@ -103,12 +106,25 @@ namespace BITKit // 设置该资源包为默认的资源包,可以使用YooAssets相关加载接口加载该资源包内容。 YooAssets.SetDefaultPackage(package); + if (package.InitializeStatus is EOperationStatus.Succeed) + { + YooAssets.DestroyPackage(package.PackageName); + } + InitializeParameters initParameters = new HostPlayModeParameters { BuildinQueryServices = buildinQueryServices, RemoteServices = remoteServices }; + if (Application.platform is RuntimePlatform.WebGLPlayer) + { + initParameters = new WebPlayModeParameters() + { + BuildinQueryServices = buildinQueryServices, + RemoteServices = remoteServices + }; + } #if UNITY_EDITOR if (IsEditorSimulateMode) { @@ -120,8 +136,21 @@ namespace BITKit EditorSimulateModeHelper.SimulateBuild("ScriptableBuildPipeline", packageName.Value) }; initParameters = editorParameters; - }else - #endif + } + else + { + switch (UnityEditor.EditorUserBuildSettings.activeBuildTarget) + { + case UnityEditor.BuildTarget.WebGL: + initParameters = new WebPlayModeParameters() + { + BuildinQueryServices = buildinQueryServices, + RemoteServices = remoteServices + }; + break; + } + } +#endif if (isOffline) { initParameters = new OfflinePlayModeParameters(); @@ -304,6 +333,9 @@ namespace BITKit SceneManager.LoadScene(1); } + + if (document) + Destroy(document); } catch (Exception e) { @@ -311,13 +343,14 @@ namespace BITKit _progressBar.value =0; _progressLabel.text = e.Message; } - if (document) - Destroy(document); + } private void OnDestroy() { - YooAssets.Destroy(); +#if UNITY_EDITOR + YooAssets.Destroy(); +#endif } } diff --git a/Src/Unity/Scripts/UX/Chart/BITKit.UX.Chart.Runtime.asmdef b/Src/Unity/Scripts/UX/Chart/BITKit.UX.Chart.Runtime.asmdef index 863e439..20e10d2 100644 --- a/Src/Unity/Scripts/UX/Chart/BITKit.UX.Chart.Runtime.asmdef +++ b/Src/Unity/Scripts/UX/Chart/BITKit.UX.Chart.Runtime.asmdef @@ -7,8 +7,6 @@ "GUID:d525ad6bd40672747bde77962f1c401e", "GUID:49b49c76ee64f6b41bf28ef951cb0e50", "GUID:d8b63aba1907145bea998dd612889d6b", - "GUID:6de01b04fa4e14662b03fa46366da151", - "GUID:f19bbd83e3c264a5680926bf75d7e494", "GUID:1c2aa13aa706ffc49a1a0044cad55436" ], "includePlatforms": [], @@ -17,9 +15,7 @@ "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, - "defineConstraints": [ - "_SkiaSharp" - ], + "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } \ No newline at end of file diff --git a/Src/Unity/Scripts/UX/Chart/SkiaChart.cs b/Src/Unity/Scripts/UX/Chart/SkiaChart.cs index a973517..f0f144d 100644 --- a/Src/Unity/Scripts/UX/Chart/SkiaChart.cs +++ b/Src/Unity/Scripts/UX/Chart/SkiaChart.cs @@ -5,14 +5,13 @@ using System.Globalization; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using SkiaSharp; using UnityEngine; using UnityEngine.UIElements; namespace BITKit.UX { - public class SkiaChart : VisualElement + public class LineChart : VisualElement { public new class UxmlTraits : VisualElement.UxmlTraits { @@ -30,27 +29,21 @@ namespace BITKit.UX public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); - var x = (SkiaChart)ve; + var x = (LineChart)ve; x.Json = m_JsonAttribute.GetValueFromBag(bag, cc); x.AllowWarning = m_allowWarningsAttribute.GetValueFromBag(bag, cc); } } - public new class UxmlFactory : UxmlFactory + public new class UxmlFactory : UxmlFactory { } - public SkiaChart() + public LineChart() { - RegisterCallback(x => - { - if (visible) - { - Rebuild(); - } - }); RegisterCallback(OnCustomStyleResolved); + style.flexDirection = FlexDirection.Row; _dataContainer = this.Create(); @@ -61,6 +54,26 @@ namespace BITKit.UX _dataContainer.name = "Data"; _chartContainer.name = "Chart"; + + _chartContainer.generateVisualContent+= GenerateVisualContent; + + _chartContainer.style.marginBottom = 0; + _chartContainer.style.marginLeft = 8; + _chartContainer.style.marginRight = 8; + _chartContainer.style.marginTop = 0 ; + + pickingMode = PickingMode.Ignore; + _dataContainer.pickingMode = PickingMode.Ignore; + + _chartContainer.style.flexDirection = FlexDirection.RowReverse; + _dataContainer.style.flexDirection = FlexDirection.ColumnReverse; + + _chartContainer.RegisterCallback(OnPointerMove); + } + + private void OnPointerMove(PointerMoveEvent evt) + { + //_chartContainer.tooltip = evt.localPosition.ToString(); } private void OnCustomStyleResolved(CustomStyleResolvedEvent evt) @@ -71,7 +84,16 @@ namespace BITKit.UX _secondaryColor = helpLineColor; } - Rebuild(); + for (int i = 0; i < 8; i++) + { + if (evt.customStyle.TryGetValue(new CustomStyleProperty($"--chart-line-color--{i}"), + out var lineColor)) + { + _lineColors[i] = lineColor; + } + } + + MarkDirtyRepaint(); } public string Json @@ -80,7 +102,7 @@ namespace BITKit.UX set { _json = value; - Rebuild(); + GenerateLayout(); } } @@ -93,7 +115,7 @@ namespace BITKit.UX set { _secondaryColor = value; - Rebuild(); + GenerateLayout(); } } @@ -102,12 +124,76 @@ namespace BITKit.UX private readonly VisualElement _dataContainer; private readonly VisualElement _chartContainer; - private void Rebuild() + private readonly Color[] _lineColors = new[] { + Color.red, + Color.red, + Color.red, + Color.red, + Color.red, + Color.red, + Color.red, + Color.red, + }; + + private void GenerateLayout() + { + JArray[] array; + float[] data; + try + { + array = JsonConvert.DeserializeObject(Json); + data = array.SelectMany(x => x.ToObject()).ToArray(); + } + catch (Exception e) + { + if (AllowWarning) + Debug.LogException(e); + return; + } + + if (data is null or { Length: 0 }) + { + if (AllowWarning) + BIT4Log.Log("Data is null or empty"); + return; + } + + + //假如min是x,max是y,将中间的差用data.Length划分 + var max = data.Max(); + var min = data.Min(); + + var difference = max - min; + + var maxLength = array.Select(x => x.Count).Max(); + + max += difference / maxLength; + min -= difference / maxLength; + + //Debug.Log($"min:{min} max:{max} difference:{difference} max:{max}"); + + _dataContainer.Clear(); + for (var i = 0; i <= maxLength; i++) + { + var label = _dataContainer.Create