using System; using System.Collections.Generic; using AlicizaX; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.UI; [AddComponentMenu("UI/Input Glyph")] public sealed class InputGlyph : InputGlyphBehaviourBase { public enum ActionSourceMode { ActionReference, HotkeyTrigger, ActionName } public enum OutputMode { Image, Text } [Serializable] public sealed class DeviceCategoryEvent { public InputDeviceWatcher.InputDeviceCategory category; public UnityEvent onMatched; public UnityEvent onNotMatched; } [Header("Source")] [SerializeField] private ActionSourceMode actionSourceMode = ActionSourceMode.ActionReference; [SerializeField] private InputActionReference actionReference; [SerializeField] private Component hotkeyTrigger; [SerializeField] private string actionName; [SerializeField] private string compositePartName; [Header("Output")] [SerializeField] private OutputMode outputMode = OutputMode.Image; [SerializeField] private Image targetImage; [SerializeField] private TMP_Text targetText; [Header("Platform Events")] [SerializeField] private List categoryEvents = new(); private Sprite _cachedSprite; private string _templateText; private string _cachedFormattedText; private string _cachedReplacementToken; private bool _hasInvokedCategoryEvent; private InputDeviceWatcher.InputDeviceCategory _lastInvokedCategory; #if UNITY_EDITOR private void OnValidate() { AutoAssignHotkeyTrigger(); AutoAssignTarget(); } #endif protected override void OnEnable() { AutoAssignHotkeyTrigger(); AutoAssignTarget(); CacheTemplateText(); base.OnEnable(); InvokeCategoryEvents(true); } protected override void OnDeviceCategoryChanged( InputDeviceWatcher.InputDeviceCategory previousCategory, InputDeviceWatcher.InputDeviceCategory newCategory) { if (previousCategory == newCategory) { return; } InvokeCategoryEvents(false); } protected override void RefreshGlyph() { InputAction action = ResolveAction(); switch (outputMode) { case OutputMode.Image: RefreshImage(action); break; case OutputMode.Text: RefreshText(action); break; } } private void RefreshImage(InputAction action) { if (targetImage == null) { return; } if (action == null) { ClearImage(); return; } bool hasSprite = GlyphService.TryGetUISpriteForActionPath(action, compositePartName, CurrentCategory, out Sprite sprite); if (!hasSprite) { sprite = null; } if (_cachedSprite != sprite || targetImage.sprite != sprite) { _cachedSprite = sprite; targetImage.sprite = sprite; } } private void RefreshText(InputAction action) { if (targetText == null) { return; } CacheTemplateText(); if (action == null) { ResetText(); return; } string replacementToken; if (GlyphService.TryGetTMPTagForActionPath(action, compositePartName, CurrentCategory, out string tag, out string displayFallback)) { replacementToken = tag; } else { replacementToken = displayFallback; } if (string.IsNullOrEmpty(replacementToken)) { ResetText(); return; } string formattedText = Utility.Text.Format(_templateText, replacementToken); if (_cachedReplacementToken == replacementToken && _cachedFormattedText == formattedText && targetText.text == formattedText) { return; } _cachedReplacementToken = replacementToken; if (_cachedFormattedText != formattedText || targetText.text != formattedText) { _cachedFormattedText = formattedText; targetText.text = formattedText; } } private InputAction ResolveAction() { switch (actionSourceMode) { case ActionSourceMode.ActionReference: return actionReference != null ? actionReference.action : null; case ActionSourceMode.HotkeyTrigger: return ResolveHotkeyAction(); case ActionSourceMode.ActionName: return InputBindingManager.TryGetAction(actionName, out InputAction action) ? action : null; default: return null; } } private InputAction ResolveHotkeyAction() { IHotkeyTrigger trigger = ResolveHotkeyTrigger(); return trigger != null && trigger.HotkeyAction != null ? trigger.HotkeyAction.action : null; } private IHotkeyTrigger ResolveHotkeyTrigger() { AutoAssignHotkeyTrigger(); return hotkeyTrigger as IHotkeyTrigger; } private void AutoAssignHotkeyTrigger() { if (actionSourceMode != ActionSourceMode.HotkeyTrigger || hotkeyTrigger != null) { return; } if (TryGetComponent(typeof(IHotkeyTrigger), out Component component)) { hotkeyTrigger = component; } } private void AutoAssignTarget() { switch (outputMode) { case OutputMode.Image: if (targetImage == null) { targetImage = GetComponent(); } break; case OutputMode.Text: if (targetText == null) { targetText = GetComponent(); } break; } } private void CacheTemplateText() { if (targetText == null) { return; } if (string.IsNullOrEmpty(_templateText)) { _templateText = targetText.text; } } private void ResetText() { _cachedReplacementToken = null; _cachedFormattedText = null; if (targetText != null && targetText.text != _templateText) { targetText.text = _templateText; } } private void ClearImage() { _cachedSprite = null; if (targetImage != null && targetImage.sprite != null) { targetImage.sprite = null; } } private void InvokeCategoryEvents(bool force) { if (!force && _hasInvokedCategoryEvent && _lastInvokedCategory == CurrentCategory) { return; } _hasInvokedCategoryEvent = true; _lastInvokedCategory = CurrentCategory; if (categoryEvents == null) { return; } for (int i = 0; i < categoryEvents.Count; i++) { DeviceCategoryEvent categoryEvent = categoryEvents[i]; if (categoryEvent == null) { continue; } if (categoryEvent.category == CurrentCategory) { categoryEvent.onMatched?.Invoke(); } else { categoryEvent.onNotMatched?.Invoke(); } } } }