using System; using System.Collections.Generic; using System.Text; using System.Threading; using BITKit.Mod; using Cysharp.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Unity.Mathematics; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace BITKit.UX { /// /// 适用于Unity的UX Service /// public class UXService : IUXService { private readonly IAfterTicker _ticker; private readonly IServiceProvider _serviceProvider; private readonly CancellationTokenSource _cancellationTokenSource; public UXService(IAfterTicker ticker, IServiceProvider serviceProvider, CancellationTokenSource cancellationTokenSource) { _ticker = ticker; _serviceProvider = serviceProvider; _cancellationTokenSource = cancellationTokenSource; _entryGroup.OnEntry += OnEntry; _windowEntryGroup.OnEntry += OnWindowEntry; _windowEntryGroup.OnExit += OnWindowExit; _ticker.Add(OnTick); } private void OnWindowExit(IUXPanel obj) { BITAppForUnity.AllowCursor.RemoveElement(_windowEntryGroup); if (obj.AllowInput is false) BITInputSystem.AllowInput.RemoveDisableElements(_windowEntryGroup); } private void OnWindowEntry(IUXPanel obj) { BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, obj.AllowCursor); if (obj.AllowInput is false) BITInputSystem.AllowInput.AddDisableElements(_windowEntryGroup); } private readonly EntryGroup _entryGroup = new(); private readonly EntryGroup _windowEntryGroup = new(); /// /// 内部注册面板队列 /// private readonly Queue _registryQueue = new(); /// /// 内部注销面板队列 /// private readonly Queue _unRegistryQueue = new(); /// /// 已注册面板字典 /// private readonly Dictionary _panels = new(); /// /// 等待启用的面板队列 /// private readonly Stack _entryQueue = new(); private readonly List _entryQueueByName = new(); /// /// 返回面板缓冲区 /// private readonly DoubleBuffer _returnBuffer = new(); /// /// 已启用面板 /// private IUXPanel _currentPanel; /// /// 历史面板 /// private static readonly Stack History = new(); /// /// 清空历史面板,通常用于关闭返回上一步 /// public static void ClearHistory() => History.Clear(); public string SettingsPath { get; set; } = "ux_panel_settings"; public object Root { get; private set; } public static VisualElement RootVisualElement { get; private set; } public async UniTask InitializeAsync() { var gameObject = new GameObject("UXService"); Object.DontDestroyOnLoad(gameObject); _cancellationTokenSource.Token.Register(() => { Object.Destroy(gameObject); }); var document = gameObject.AddComponent(); try { if (Touchscreen.current is not null && SettingsPath == "ux_panel_settings") { SettingsPath = "ux_panel_settings_mobile"; } var panelSettings =await ModService.LoadAsset(SettingsPath); document.panelSettings = panelSettings; } catch (Exception e) { BIT4Log.Warning("未找到ux_panel_settings"); throw; } Root = RootVisualElement= document.rootVisualElement; if (Touchscreen.current is not null) { RootVisualElement.AddToClassList("mobile"); } } public void Register(IUXPanel panel) => _registryQueue.Enqueue(panel); public void UnRegister(IUXPanel panel) => _unRegistryQueue.Enqueue(panel); public void Entry() where T : IUXPanel { var panel = _serviceProvider.GetRequiredService(); Entry(panel); //Entry(typeof(T).Name); } public void Entry(IUXPanel panel) => _entryQueue.Push(panel); public void Entry(string panelName) => _entryQueueByName.TryAdd(panelName); public IUXPanel CurrentPanel => _currentPanel; public event Action OnPanelChanged; public bool TryPick(float2 position, out object obj) { obj = null; if (!EventSystem.current.IsPointerOverGameObject()) { return false; } position.y = Screen.height - position.y; var ve = RootVisualElement.panel.Pick(RuntimePanelUtils.ScreenToPanel(RootVisualElement.panel, position)); obj = ve; return obj is not null; } public void Return() { if(_windowEntryGroup.TryGetEntried(out _)) { _windowEntryGroup.Entry(-1); return; } if (History.TryPop(out var returnPanel)) { _returnBuffer.Release(returnPanel); } } private bool _initialized; private void OnEntry(IUXPanel obj) { OnPanelChanged?.Invoke(_currentPanel,obj); _currentPanel = obj; } private void OnTick(float delta) { try { while (_registryQueue.TryDequeue(out var result)) { if (result is null) continue; _entryGroup.list.Add(result); _panels.Set(result.Index, result); } while (_unRegistryQueue.TryDequeue(out var result)) { if (result is null) continue; _entryGroup.list.Remove(result); _panels.Remove(result.Index); } if (_returnBuffer.TryGetRelease(out var returnPanel)) { _entryGroup.Entry(x=>x.Index==returnPanel.Index); BITAppForUnity.AllowCursor.SetElements(this, returnPanel.AllowCursor); BITInputSystem.AllowInput.SetElements(this, returnPanel.AllowInput); } foreach (var panelName in _entryQueueByName) { if (!_panels.TryGetValue(panelName, out var panel))continue; _entryQueue.Push(panel); _entryQueueByName.TryRemove(panelName); break; } if (_entryQueue.TryPop(out var nextPanel)) { if (nextPanel.IsWindow) { _windowEntryGroup.Entry(nextPanel); return; } _windowEntryGroup.Entry(-1); History.Push(_currentPanel); _entryGroup.Entry(x=>x.Index==nextPanel.Index); BITAppForUnity.AllowCursor.SetElements(this, nextPanel.AllowCursor); BITInputSystem.AllowInput.SetElements(this, nextPanel.AllowInput); } if (_entryGroup.TryGetEntried(out var currentPanel)) { currentPanel.OnTick(Time.deltaTime); } if(_windowEntryGroup.TryGetEntried(out var windowPanel)) { windowPanel.OnTick(Time.deltaTime); } } catch (Exception e) { BIT4Log.LogException(e); } } public async void Dispose() { foreach (var panelsValue in _panels.Values) { if (panelsValue is IDisposable disposable) { disposable.Dispose(); } } _ticker.Remove(OnTick); await UniTask.SwitchToMainThread(); if (_currentPanel is not null) { // ReSharper disable once MethodHasAsyncOverload _currentPanel.Exit(); await _currentPanel.ExitAsync(); _currentPanel.Exited(); } } } }