304 lines
10 KiB
C#
304 lines
10 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using TMPro;
|
||
|
|
using UnityEditor;
|
||
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.InputSystem;
|
||
|
|
|
||
|
|
[CustomEditor(typeof(InputGlyph))]
|
||
|
|
[CanEditMultipleObjects]
|
||
|
|
public sealed class InputGlyphEditor : Editor
|
||
|
|
{
|
||
|
|
private SerializedProperty _actionSourceMode;
|
||
|
|
private SerializedProperty _actionReference;
|
||
|
|
private SerializedProperty _hotkeyTrigger;
|
||
|
|
private SerializedProperty _actionName;
|
||
|
|
private SerializedProperty _compositePartName;
|
||
|
|
private SerializedProperty _outputMode;
|
||
|
|
private SerializedProperty _targetImage;
|
||
|
|
private SerializedProperty _targetText;
|
||
|
|
private SerializedProperty _categoryEvents;
|
||
|
|
|
||
|
|
private GUIStyle _titleStyle;
|
||
|
|
private GUIStyle _sectionStyle;
|
||
|
|
private GUIStyle _hintStyle;
|
||
|
|
|
||
|
|
private void OnEnable()
|
||
|
|
{
|
||
|
|
_actionSourceMode = serializedObject.FindProperty("actionSourceMode");
|
||
|
|
_actionReference = serializedObject.FindProperty("actionReference");
|
||
|
|
_hotkeyTrigger = serializedObject.FindProperty("hotkeyTrigger");
|
||
|
|
_actionName = serializedObject.FindProperty("actionName");
|
||
|
|
_compositePartName = serializedObject.FindProperty("compositePartName");
|
||
|
|
_outputMode = serializedObject.FindProperty("outputMode");
|
||
|
|
_targetImage = serializedObject.FindProperty("targetImage");
|
||
|
|
_targetText = serializedObject.FindProperty("targetText");
|
||
|
|
_categoryEvents = serializedObject.FindProperty("categoryEvents");
|
||
|
|
BuildStyles();
|
||
|
|
}
|
||
|
|
|
||
|
|
public override void OnInspectorGUI()
|
||
|
|
{
|
||
|
|
serializedObject.Update();
|
||
|
|
|
||
|
|
DrawSourceSection();
|
||
|
|
DrawOutputSection();
|
||
|
|
DrawEventsSection();
|
||
|
|
|
||
|
|
serializedObject.ApplyModifiedProperties();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private void DrawSourceSection()
|
||
|
|
{
|
||
|
|
InputAction resolvedAction = ResolveSelectedAction();
|
||
|
|
|
||
|
|
EditorGUILayout.BeginVertical(_sectionStyle);
|
||
|
|
EditorGUILayout.PropertyField(_actionSourceMode, new GUIContent("Reference Mode"));
|
||
|
|
DrawSourceFields();
|
||
|
|
DrawResolvedActionInfo(resolvedAction);
|
||
|
|
DrawCompositePartField(resolvedAction);
|
||
|
|
EditorGUILayout.EndVertical();
|
||
|
|
EditorGUILayout.Space(6f);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawSourceFields()
|
||
|
|
{
|
||
|
|
InputGlyph.ActionSourceMode mode = (InputGlyph.ActionSourceMode)_actionSourceMode.enumValueIndex;
|
||
|
|
switch (mode)
|
||
|
|
{
|
||
|
|
case InputGlyph.ActionSourceMode.ActionReference:
|
||
|
|
EditorGUILayout.PropertyField(_actionReference, new GUIContent("Action Reference"));
|
||
|
|
EditorGUILayout.LabelField("Use a direct InputActionReference.", _hintStyle);
|
||
|
|
break;
|
||
|
|
case InputGlyph.ActionSourceMode.HotkeyTrigger:
|
||
|
|
EditorGUILayout.PropertyField(_hotkeyTrigger, new GUIContent("Hotkey Trigger"));
|
||
|
|
Component component = _hotkeyTrigger.objectReferenceValue as Component;
|
||
|
|
if (component != null && !(component is UnityEngine.UI.IHotkeyTrigger))
|
||
|
|
{
|
||
|
|
EditorGUILayout.HelpBox("Hotkey Trigger must implement IHotkeyTrigger.", MessageType.Warning);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
EditorGUILayout.LabelField("Reads the action from an external IHotkeyTrigger component.", _hintStyle);
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
case InputGlyph.ActionSourceMode.ActionName:
|
||
|
|
EditorGUILayout.PropertyField(_actionName, new GUIContent("Action Name"));
|
||
|
|
EditorGUILayout.LabelField("Supports ActionName or MapName/ActionName.", _hintStyle);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawOutputSection()
|
||
|
|
{
|
||
|
|
EditorGUILayout.BeginVertical(_sectionStyle);
|
||
|
|
EditorGUILayout.PropertyField(_outputMode, new GUIContent("Render Mode"));
|
||
|
|
DrawOutputFields();
|
||
|
|
EditorGUILayout.EndVertical();
|
||
|
|
EditorGUILayout.Space(6f);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private void DrawOutputFields()
|
||
|
|
{
|
||
|
|
InputGlyph.OutputMode mode = (InputGlyph.OutputMode)_outputMode.enumValueIndex;
|
||
|
|
switch (mode)
|
||
|
|
{
|
||
|
|
case InputGlyph.OutputMode.Image:
|
||
|
|
EditorGUILayout.PropertyField(_targetImage, new GUIContent("Target Image"));
|
||
|
|
EditorGUILayout.LabelField("Shows the resolved sprite on a Unity UI Image.", _hintStyle);
|
||
|
|
break;
|
||
|
|
case InputGlyph.OutputMode.Text:
|
||
|
|
EditorGUILayout.PropertyField(_targetText, new GUIContent("Target TMP Text"));
|
||
|
|
EditorGUILayout.LabelField("Uses the current TMP text as a template and replaces {0}.", _hintStyle);
|
||
|
|
TMP_Text text = _targetText.objectReferenceValue as TMP_Text;
|
||
|
|
if (text == null)
|
||
|
|
{
|
||
|
|
EditorGUILayout.HelpBox("If TMP_Text is empty, the component tries GetComponent<TMP_Text>().", MessageType.None);
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawEventsSection()
|
||
|
|
{
|
||
|
|
EditorGUILayout.BeginVertical(_sectionStyle);
|
||
|
|
EditorGUILayout.PropertyField(_categoryEvents, new GUIContent("Category Events"), true);
|
||
|
|
EditorGUILayout.EndVertical();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawResolvedActionInfo(InputAction action)
|
||
|
|
{
|
||
|
|
if (action == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
string mapName = action.actionMap != null ? action.actionMap.name : "<No Map>";
|
||
|
|
EditorGUILayout.LabelField($"Resolved Action: {mapName}/{action.name}", _hintStyle);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DrawCompositePartField(InputAction action)
|
||
|
|
{
|
||
|
|
List<string> compositeParts = CollectCompositePartNames(action);
|
||
|
|
if (compositeParts.Count == 0)
|
||
|
|
{
|
||
|
|
if (!string.IsNullOrEmpty(_compositePartName.stringValue))
|
||
|
|
{
|
||
|
|
_compositePartName.stringValue = string.Empty;
|
||
|
|
}
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
string[] options = new string[compositeParts.Count + 1];
|
||
|
|
options[0] = "<None>";
|
||
|
|
for (int i = 0; i < compositeParts.Count; i++)
|
||
|
|
{
|
||
|
|
options[i + 1] = compositeParts[i];
|
||
|
|
}
|
||
|
|
|
||
|
|
int selectedIndex = 0;
|
||
|
|
for (int i = 0; i < compositeParts.Count; i++)
|
||
|
|
{
|
||
|
|
if (string.Equals(compositeParts[i], _compositePartName.stringValue, StringComparison.OrdinalIgnoreCase))
|
||
|
|
{
|
||
|
|
selectedIndex = i + 1;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int newIndex = EditorGUILayout.Popup(new GUIContent("Composite Part"), selectedIndex, options);
|
||
|
|
_compositePartName.stringValue = newIndex <= 0 ? string.Empty : compositeParts[newIndex - 1];
|
||
|
|
EditorGUILayout.LabelField("Shown only when the resolved action contains composite bindings.", _hintStyle);
|
||
|
|
}
|
||
|
|
|
||
|
|
private InputAction ResolveSelectedAction()
|
||
|
|
{
|
||
|
|
InputGlyph.ActionSourceMode mode = (InputGlyph.ActionSourceMode)_actionSourceMode.enumValueIndex;
|
||
|
|
switch (mode)
|
||
|
|
{
|
||
|
|
case InputGlyph.ActionSourceMode.ActionReference:
|
||
|
|
InputActionReference actionReference = _actionReference.objectReferenceValue as InputActionReference;
|
||
|
|
return actionReference != null ? actionReference.action : null;
|
||
|
|
case InputGlyph.ActionSourceMode.HotkeyTrigger:
|
||
|
|
Component component = _hotkeyTrigger.objectReferenceValue as Component;
|
||
|
|
if (component is UnityEngine.UI.IHotkeyTrigger trigger && trigger.HotkeyAction != null)
|
||
|
|
{
|
||
|
|
return trigger.HotkeyAction.action;
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
case InputGlyph.ActionSourceMode.ActionName:
|
||
|
|
return ResolveActionByName(_actionName.stringValue);
|
||
|
|
default:
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private InputAction ResolveActionByName(string actionName)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(actionName))
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (InputActionAsset asset in EnumerateInputActionAssets())
|
||
|
|
{
|
||
|
|
if (asset == null)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
InputAction action = asset.FindAction(actionName, false);
|
||
|
|
if (action != null)
|
||
|
|
{
|
||
|
|
return action;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private IEnumerable<InputActionAsset> EnumerateInputActionAssets()
|
||
|
|
{
|
||
|
|
HashSet<InputActionAsset> visited = new HashSet<InputActionAsset>();
|
||
|
|
InputBindingManager[] managers = Resources.FindObjectsOfTypeAll<InputBindingManager>();
|
||
|
|
for (int i = 0; i < managers.Length; i++)
|
||
|
|
{
|
||
|
|
InputActionAsset asset = managers[i] != null ? managers[i].actions : null;
|
||
|
|
if (asset != null && visited.Add(asset))
|
||
|
|
{
|
||
|
|
yield return asset;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
string[] guids = AssetDatabase.FindAssets("t:InputActionAsset");
|
||
|
|
for (int i = 0; i < guids.Length; i++)
|
||
|
|
{
|
||
|
|
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
|
||
|
|
InputActionAsset asset = AssetDatabase.LoadAssetAtPath<InputActionAsset>(path);
|
||
|
|
if (asset != null && visited.Add(asset))
|
||
|
|
{
|
||
|
|
yield return asset;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static List<string> CollectCompositePartNames(InputAction action)
|
||
|
|
{
|
||
|
|
List<string> parts = new List<string>();
|
||
|
|
if (action == null)
|
||
|
|
{
|
||
|
|
return parts;
|
||
|
|
}
|
||
|
|
|
||
|
|
HashSet<string> uniqueParts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
|
|
for (int i = 0; i < action.bindings.Count; i++)
|
||
|
|
{
|
||
|
|
InputBinding binding = action.bindings[i];
|
||
|
|
if (!binding.isPartOfComposite || string.IsNullOrWhiteSpace(binding.name))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (uniqueParts.Add(binding.name))
|
||
|
|
{
|
||
|
|
parts.Add(binding.name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return parts;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void BuildStyles()
|
||
|
|
{
|
||
|
|
if (_titleStyle == null)
|
||
|
|
{
|
||
|
|
_titleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||
|
|
{
|
||
|
|
fontSize = 14
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_sectionStyle == null)
|
||
|
|
{
|
||
|
|
_sectionStyle = new GUIStyle(EditorStyles.helpBox)
|
||
|
|
{
|
||
|
|
padding = new RectOffset(12, 12, 10, 10)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_hintStyle == null)
|
||
|
|
{
|
||
|
|
_hintStyle = new GUIStyle(EditorStyles.miniLabel)
|
||
|
|
{
|
||
|
|
wordWrap = true
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|