using System; using System.Collections.Generic; using System.ComponentModel.Design; using UnityEngine; using UnityEngine.UIElements; using UnityEngine.InputSystem; using BITKit.Mod; using Cysharp.Threading.Tasks; using Object = UnityEngine.Object; namespace BITKit.UX.Internal { public class ContextMenu : IContextMenu { public virtual VisualElement GetVisualElement() { return new VisualElement(); } } public class Label : ContextMenu { public Label(string text) { _text = text; } private readonly string _text; public override VisualElement GetVisualElement() { UnityEngine.UIElements.Label label = new(_text); return label; } } [Serializable] public class TestContextMenu { public void Execute() { ContextMenuBuilder .Create() .BuildText("Tools") .BuildAction("Log", () => Debug.Log(nameof(ContextMenu))) .Build(); } } public class Button : ContextMenu { public Button(string text, params Action[] actions) { _text = text; foreach (var x in actions) { _action += x; } } private readonly string _text; private readonly Action _action; public override VisualElement GetVisualElement() { UnityEngine.UIElements.Button button = new(); button.clicked += _action; button.text = _text; return button; } } } namespace BITKit.UX { public interface IContextMenu { VisualElement GetVisualElement(); } public static class ContextMenuBuilderExtensions { public static ContextMenuBuilder BuildText(this ContextMenuBuilder self, string text) { self.Add(new Internal.Label(text)); return self; } public static ContextMenuBuilder BuildAction(this ContextMenuBuilder self, string text, Action action) { self.Add(new Internal.Button(text, action, self.Execute)); return self; } } public class ContextMenuBuilder { private readonly List _contexts = new(); public event Action OnExecuted; private ContextMenuBuilder() { } public static ContextMenuBuilder Create() { return new(); } public void Build() { UXContextMenu.Singleton.Create(this); } internal void Execute() { OnExecuted?.Invoke(); } public void Add(IContextMenu x) => _contexts.Add(x); public IEnumerable GetContextMenus() => _contexts.ToArray(); } public class UXContextMenu:IDisposable { internal static UXContextMenu Singleton; private readonly IUXService _uxService; private VisualElement _root; private VisualElement _container; private readonly UniTaskCompletionSource _waitUntilInitialized = new(); public UXContextMenu(IUXService uxService) { _uxService = uxService; Singleton = this; uxService.OnPanelChanged += OnPanelChanged; InitializeAsync(); } private async void InitializeAsync() { var go = new GameObject(nameof(UXContextMenu)); Object.DontDestroyOnLoad(go); var document = go.AddComponent(); document.sortingOrder = 1; try { var panelSettings =await ModService.LoadAsset("ux_panel_settings"); document.panelSettings = panelSettings; } catch (Exception) { BIT4Log.Warning("未找到ux_panel_settings"); throw; } document.visualTreeAsset = await ModService.LoadAsset("ui_context_menu"); _root = document.rootVisualElement; _container = _root.Q("menu-container"); _root.Q("background-image").RegisterCallback(_ => { Close(); }); _waitUntilInitialized.TrySetResult(); Close(); } 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); _container.style.position = Position.Absolute; _container.style.left = pos.x; _container.style.top = pos.y; _container.Clear(); _root.SetActive(true); foreach (var context in builder.GetContextMenus()) { _container.Add(context.GetVisualElement()); } builder.OnExecuted += Close; } private async void Close() { await _waitUntilInitialized.Task; _container.Clear(); _root.SetActive(false); } public void Dispose() { _uxService.OnPanelChanged -= OnPanelChanged; } private void OnPanelChanged(IUXPanel arg1, IUXPanel arg2) { Close(); } } }