using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using Sirenix.Utilities; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UIElements; namespace VOID.UserInterface { [DefaultExecutionOrder(-1)] public class GameUI : MonoBehaviour { public static GameUI current { get; private set; } [Title("Generic Templates:")] public VisualTreeAsset slotTemplate => _slotTemplate; public VisualTreeAsset statusTemplate => _statusTemplate; public VisualTreeAsset unitPortraitTemplate => _unitPortraitTemplate; public VisualTreeAsset levelUpOptionTemplate => _levelUpOptionTemplate; public VisualTreeAsset profileTemplate => _profileTemplate; [SerializeField, Required] private VisualTreeAsset _slotTemplate; [SerializeField, Required] private VisualTreeAsset _statusTemplate; [SerializeField, Required] private VisualTreeAsset _unitPortraitTemplate; [SerializeField, Required] private VisualTreeAsset _levelUpOptionTemplate; [SerializeField, Required] private VisualTreeAsset _profileTemplate; private VisualElement _rootVisualElement; public IPanel runtimePanel { get; private set; } public PanelSettings panelSettings { get; private set; } public Vector2 cursorPositionOnUI { get; private set; } public bool isCursorOverUI { get; private set; } public float scale { get; private set; } // Visual elements under cursor + event when Visual element under cursor changes public readonly List elementsUnderCursor = new(); public event Action ElementsUnderCursorChanged; private void Awake() { current = this; var uiDocument = gameObject.GetComponent(); _rootVisualElement = uiDocument.rootVisualElement; panelSettings = uiDocument.panelSettings; runtimePanel = _rootVisualElement.panel; CalculateScale(); } public VisualElement PickTopElementUI(List cachedElements = null) { return (cachedElements ?? elementsUnderCursor).FirstOrDefault(); } public T PickComponentUI(List cachedElements = null) { return (cachedElements ?? elementsUnderCursor).Select(el => el.dataSource).FilterCast().FirstOrDefault(); } public T PickTopComponentUI(List cachedElements = null) { return PickTopComponentUI(cachedElements) is T dataSource ? dataSource : default; } public object PickTopComponentUI(List cachedElements = null) { return PickTopElementUI(cachedElements)?.dataSource; } private void CalculateScale() { var screenSize = new Vector2(Screen.width, Screen.height); var referenceScreenSize = panelSettings.referenceResolution; var proportions = referenceScreenSize / screenSize; scale = proportions.x * panelSettings.match + proportions.y * (1f - panelSettings.match); } private void Update() { FindCursorPositionOnUI(); FindElementsUnderCursor(); isCursorOverUI = !elementsUnderCursor.IsNullOrEmpty(); } private void FindCursorPositionOnUI() { var mousePos = Mouse.current.position.ReadValue(); mousePos.y = Screen.height - mousePos.y; cursorPositionOnUI = RuntimePanelUtils.ScreenToPanel(runtimePanel, mousePos); } private void FindElementsUnderCursor() { var previousTopElement = elementsUnderCursor.FirstOrDefault(); var currentTopElement = _rootVisualElement.panel.Pick(cursorPositionOnUI); elementsUnderCursor.Clear(); // Find all "touchable" elements in UI under cursor - those with PickingMode.Position // Move through parents (in case when position absolute is used) var element = currentTopElement; while (element != null) { if (element.pickingMode is PickingMode.Position) elementsUnderCursor.Add(element); element = element.parent; } if (previousTopElement != currentTopElement) ElementsUnderCursorChanged?.Invoke(); } } }