310 lines
10 KiB
C#
310 lines
10 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
public static class GlyphService
|
|
{
|
|
private static readonly string[] KeyboardGroupHints = { "keyboard", "mouse", "keyboard&mouse", "keyboardmouse", "kbm" };
|
|
private static readonly string[] XboxGroupHints = { "xbox", "xinput", "gamepad", "controller" };
|
|
private static readonly string[] PlayStationGroupHints = { "playstation", "dualshock", "dualsense", "gamepad", "controller" };
|
|
private static readonly string[] OtherGamepadGroupHints = { "gamepad", "controller", "joystick" };
|
|
private static readonly char[] TrimChars = { '{', '}', '<', '>', '\'', '"' };
|
|
|
|
private static InputGlyphDatabase _database;
|
|
|
|
public static InputGlyphDatabase Database
|
|
{
|
|
get
|
|
{
|
|
if (_database == null)
|
|
{
|
|
_database = Resources.Load<InputGlyphDatabase>("InputGlyphDatabase");
|
|
}
|
|
|
|
return _database;
|
|
}
|
|
}
|
|
|
|
public static string GetBindingControlPath(
|
|
InputAction action,
|
|
string compositePartName = null,
|
|
InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
|
{
|
|
return TryGetBindingControl(action, compositePartName, deviceOverride, out InputBinding binding)
|
|
? GetEffectivePath(binding)
|
|
: string.Empty;
|
|
}
|
|
|
|
public static string GetBindingControlPath(
|
|
InputActionReference actionReference,
|
|
string compositePartName = null,
|
|
InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
|
{
|
|
return GetBindingControlPath(actionReference != null ? actionReference.action : null, compositePartName, deviceOverride);
|
|
}
|
|
|
|
public static bool TryGetTMPTagForActionPath(
|
|
InputAction action,
|
|
string compositePartName,
|
|
InputDeviceWatcher.InputDeviceCategory device,
|
|
out string tag,
|
|
out string displayFallback,
|
|
InputGlyphDatabase db = null)
|
|
{
|
|
string controlPath = GetBindingControlPath(action, compositePartName, device);
|
|
return TryGetTMPTagForActionPath(controlPath, device, out tag, out displayFallback, db);
|
|
}
|
|
|
|
public static bool TryGetTMPTagForActionPath(
|
|
InputActionReference actionReference,
|
|
string compositePartName,
|
|
InputDeviceWatcher.InputDeviceCategory device,
|
|
out string tag,
|
|
out string displayFallback,
|
|
InputGlyphDatabase db = null)
|
|
{
|
|
return TryGetTMPTagForActionPath(actionReference != null ? actionReference.action : null, compositePartName, device, out tag, out displayFallback, db);
|
|
}
|
|
|
|
public static bool TryGetUISpriteForActionPath(
|
|
InputAction action,
|
|
string compositePartName,
|
|
InputDeviceWatcher.InputDeviceCategory device,
|
|
out Sprite sprite,
|
|
InputGlyphDatabase db = null)
|
|
{
|
|
string controlPath = GetBindingControlPath(action, compositePartName, device);
|
|
return TryGetUISpriteForActionPath(controlPath, device, out sprite, db);
|
|
}
|
|
|
|
public static bool TryGetUISpriteForActionPath(
|
|
InputActionReference actionReference,
|
|
string compositePartName,
|
|
InputDeviceWatcher.InputDeviceCategory device,
|
|
out Sprite sprite,
|
|
InputGlyphDatabase db = null)
|
|
{
|
|
return TryGetUISpriteForActionPath(actionReference != null ? actionReference.action : null, compositePartName, device, out sprite, db);
|
|
}
|
|
|
|
public static bool TryGetTMPTagForActionPath(
|
|
string controlPath,
|
|
InputDeviceWatcher.InputDeviceCategory device,
|
|
out string tag,
|
|
out string displayFallback,
|
|
InputGlyphDatabase db = null)
|
|
{
|
|
displayFallback = GetDisplayNameFromControlPath(controlPath);
|
|
tag = null;
|
|
|
|
if (!TryGetUISpriteForActionPath(controlPath, device, out Sprite sprite, db))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
tag = $"<sprite name=\"{sprite.name}\">";
|
|
return true;
|
|
}
|
|
|
|
public static bool TryGetUISpriteForActionPath(
|
|
string controlPath,
|
|
InputDeviceWatcher.InputDeviceCategory device,
|
|
out Sprite sprite,
|
|
InputGlyphDatabase db = null)
|
|
{
|
|
sprite = null;
|
|
db ??= Database;
|
|
return db != null && db.TryGetSprite(controlPath, device, out sprite);
|
|
}
|
|
|
|
public static string GetDisplayNameFromInputAction(
|
|
InputAction action,
|
|
string compositePartName = null,
|
|
InputDeviceWatcher.InputDeviceCategory? deviceOverride = null)
|
|
{
|
|
if (!TryGetBindingControl(action, compositePartName, deviceOverride, out InputBinding binding))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
string display = binding.ToDisplayString();
|
|
return string.IsNullOrEmpty(display) ? GetDisplayNameFromControlPath(GetEffectivePath(binding)) : display;
|
|
}
|
|
|
|
public static string GetDisplayNameFromControlPath(string controlPath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(controlPath))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
string[] parts = controlPath.Split('/');
|
|
string last = parts[parts.Length - 1].Trim(TrimChars);
|
|
return last;
|
|
}
|
|
|
|
public static bool TryGetBindingControl(
|
|
InputAction action,
|
|
string compositePartName,
|
|
InputDeviceWatcher.InputDeviceCategory? deviceOverride,
|
|
out InputBinding binding)
|
|
{
|
|
binding = default;
|
|
if (action == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
InputDeviceWatcher.InputDeviceCategory category = deviceOverride ?? InputDeviceWatcher.CurrentCategory;
|
|
int bestScore = int.MinValue;
|
|
bool requireCompositePart = !string.IsNullOrEmpty(compositePartName);
|
|
|
|
for (int i = 0; i < action.bindings.Count; i++)
|
|
{
|
|
InputBinding candidate = action.bindings[i];
|
|
if (candidate.isComposite)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (requireCompositePart)
|
|
{
|
|
if (!candidate.isPartOfComposite || !string.Equals(candidate.name, compositePartName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (candidate.isPartOfComposite)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string path = GetEffectivePath(candidate);
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int score = ScoreBinding(candidate, category);
|
|
if (score > bestScore)
|
|
{
|
|
bestScore = score;
|
|
binding = candidate;
|
|
}
|
|
}
|
|
|
|
return bestScore > int.MinValue;
|
|
}
|
|
|
|
private static int ScoreBinding(InputBinding binding, InputDeviceWatcher.InputDeviceCategory category)
|
|
{
|
|
int score = 0;
|
|
string path = GetEffectivePath(binding);
|
|
|
|
if (MatchesBindingGroups(binding.groups, category))
|
|
{
|
|
score += 100;
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(binding.groups))
|
|
{
|
|
score -= 20;
|
|
}
|
|
|
|
if (MatchesControlPath(path, category))
|
|
{
|
|
score += 60;
|
|
}
|
|
|
|
if (!binding.isPartOfComposite)
|
|
{
|
|
score += 5;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
private static bool MatchesBindingGroups(string groups, InputDeviceWatcher.InputDeviceCategory category)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(groups))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string[] hints = GetGroupHints(category);
|
|
string[] tokens = groups.Split(InputBinding.Separator);
|
|
for (int i = 0; i < tokens.Length; i++)
|
|
{
|
|
string token = tokens[i].Trim();
|
|
if (ContainsAny(token, hints))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool MatchesControlPath(string path, InputDeviceWatcher.InputDeviceCategory category)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch (category)
|
|
{
|
|
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
|
return StartsWithDevice(path, "<Keyboard>") || StartsWithDevice(path, "<Mouse>");
|
|
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
|
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, XboxGroupHints);
|
|
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
|
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, PlayStationGroupHints);
|
|
default:
|
|
return StartsWithDevice(path, "<Gamepad>") || StartsWithDevice(path, "<Joystick>") || ContainsAny(path, OtherGamepadGroupHints);
|
|
}
|
|
}
|
|
|
|
private static bool StartsWithDevice(string path, string deviceTag)
|
|
{
|
|
return path.StartsWith(deviceTag, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static string[] GetGroupHints(InputDeviceWatcher.InputDeviceCategory category)
|
|
{
|
|
switch (category)
|
|
{
|
|
case InputDeviceWatcher.InputDeviceCategory.Keyboard:
|
|
return KeyboardGroupHints;
|
|
case InputDeviceWatcher.InputDeviceCategory.Xbox:
|
|
return XboxGroupHints;
|
|
case InputDeviceWatcher.InputDeviceCategory.PlayStation:
|
|
return PlayStationGroupHints;
|
|
default:
|
|
return OtherGamepadGroupHints;
|
|
}
|
|
}
|
|
|
|
private static bool ContainsAny(string source, string[] hints)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(source) || hints == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < hints.Length; i++)
|
|
{
|
|
if (source.IndexOf(hints[i], StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static string GetEffectivePath(InputBinding binding)
|
|
{
|
|
return string.IsNullOrWhiteSpace(binding.effectivePath) ? binding.path : binding.effectivePath;
|
|
}
|
|
}
|