适配UI框架的热键系统
This commit is contained in:
parent
e7b0f64c05
commit
4364f4673d
@ -1,99 +0,0 @@
|
||||
#if INPUTSYSTEM_SUPPORT
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AlicizaX.UI.Runtime;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEditor.UI
|
||||
{
|
||||
[CustomEditor(typeof(HotkeyBindComponent))]
|
||||
public class HotkeyBindComponentInspector : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty hotButtonsProp;
|
||||
private HotkeyBindComponent _target;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_target = (HotkeyBindComponent)target;
|
||||
hotButtonsProp = serializedObject.FindProperty("hotButtons");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
var holder = _target.GetComponent<UIHolderObjectBase>();
|
||||
if (holder == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"⚠ 当前对象缺少 UIHolderObjectBase 组件。\nHotkeyBindComponent 依赖它进行热键绑定事件。",
|
||||
MessageType.Error
|
||||
);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 灰掉显示 hotButtons 列表(不可手动编辑)
|
||||
GUI.enabled = false;
|
||||
DrawHotButtonListAlwaysExpanded(hotButtonsProp);
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 按钮:扫描子物体(包含隐藏)
|
||||
if (GUILayout.Button("🔍 扫描所有子物体 (包含隐藏对象)"))
|
||||
{
|
||||
FindAllUXHotkeys();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义展开显示 hotButtons 列表
|
||||
/// </summary>
|
||||
private void DrawHotButtonListAlwaysExpanded(SerializedProperty listProp)
|
||||
{
|
||||
EditorGUILayout.LabelField("Hot Buttons", EditorStyles.boldLabel);
|
||||
|
||||
if (listProp == null || listProp.arraySize == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("当前没有绑定任何 UXHotkey。", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
for (int i = 0; i < listProp.arraySize; i++)
|
||||
{
|
||||
var element = listProp.GetArrayElementAtIndex(i);
|
||||
var comp = element.objectReferenceValue as Component;
|
||||
string name = comp != null ? comp.name + " (" + comp.GetType().Name + ")" : "Null";
|
||||
// 注意:这里用 typeof(Component)
|
||||
EditorGUILayout.ObjectField($"[{i}] {name}", element.objectReferenceValue, typeof(Component), true);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找所有子物体(包含隐藏)并绑定 UXHotkey
|
||||
/// </summary>
|
||||
private void FindAllUXHotkeys()
|
||||
{
|
||||
Undo.RecordObject(_target, "Scan UXHotkey");
|
||||
|
||||
var collectMethod = target.GetType().GetMethod("CollectUXHotkeys", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (collectMethod != null)
|
||||
{
|
||||
collectMethod.Invoke(target, null);
|
||||
EditorUtility.SetDirty(_target);
|
||||
serializedObject.Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("未找到 CollectUXHotkeys 方法。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9779e310e684e9497bdac4f4ac3983e
|
||||
timeCreated: 1760340548
|
||||
@ -1,3 +1,4 @@
|
||||
using AlicizaX.UI.Runtime;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
@ -21,10 +22,25 @@ namespace UnityEditor.UI
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
HotkeyComponent hotkeyComponent = (HotkeyComponent)target;
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"Hotkeys auto-register to the nearest UIHolderObjectBase at runtime.",
|
||||
MessageType.Info
|
||||
);
|
||||
|
||||
if (hotkeyComponent.GetComponentInParent<UIHolderObjectBase>(true) == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"No UIHolderObjectBase was found in parents. This hotkey will not register at runtime.",
|
||||
MessageType.Warning
|
||||
);
|
||||
}
|
||||
|
||||
if (_component.objectReferenceValue == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("物体身上不存在ISubmitHandler得到组件", MessageType.Error);
|
||||
HotkeyComponent hotkeyComponent = (HotkeyComponent)target;
|
||||
EditorGUILayout.HelpBox("No submit target was found on this object.", MessageType.Error);
|
||||
if (hotkeyComponent.TryGetComponent(typeof(ISubmitHandler), out Component submitHandler))
|
||||
{
|
||||
_component.objectReferenceValue = submitHandler;
|
||||
@ -39,8 +55,8 @@ namespace UnityEditor.UI
|
||||
EditorGUILayout.PropertyField(_component, new GUIContent("Component"));
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUILayout.PropertyField(_hotkeyAction, new GUIContent("输入映射"));
|
||||
EditorGUILayout.PropertyField(_hotkeyPressType, new GUIContent("触发类型"));
|
||||
EditorGUILayout.PropertyField(_hotkeyAction, new GUIContent("Input Action"));
|
||||
EditorGUILayout.PropertyField(_hotkeyPressType, new GUIContent("Press Type"));
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
#if INPUTSYSTEM_SUPPORT
|
||||
using System.Linq;
|
||||
using AlicizaX.UI.Runtime;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.UI
|
||||
{
|
||||
public class HotkeyBindComponent : MonoBehaviour
|
||||
{
|
||||
private UIHolderObjectBase _holderObjectBase;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_holderObjectBase = GetComponent<UIHolderObjectBase>();
|
||||
_holderObjectBase.OnWindowBeforeShowEvent += BindHotKeys;
|
||||
_holderObjectBase.OnWindowBeforeClosedEvent += UnBindHotKeys;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_holderObjectBase != null)
|
||||
{
|
||||
_holderObjectBase.OnWindowBeforeShowEvent -= BindHotKeys;
|
||||
_holderObjectBase.OnWindowBeforeClosedEvent -= UnBindHotKeys;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] private Component[] hotButtons;
|
||||
|
||||
internal void BindHotKeys()
|
||||
{
|
||||
if (hotButtons == null) return;
|
||||
for (int i = 0; i < hotButtons.Length; i++)
|
||||
{
|
||||
if (hotButtons[i] is IHotkeyTrigger trigger)
|
||||
{
|
||||
trigger.BindHotKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[ContextMenu("Bind HotKeys")]
|
||||
private void CollectUXHotkeys()
|
||||
{
|
||||
var found = gameObject
|
||||
.GetComponentsInChildren<MonoBehaviour>(true)
|
||||
.OfType<IHotkeyTrigger>()
|
||||
.Where(t => t.HotkeyAction != null)
|
||||
.Select(t => t as Component)
|
||||
.ToArray();
|
||||
|
||||
hotButtons = found;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_holderObjectBase == null)
|
||||
{
|
||||
_holderObjectBase = gameObject.GetComponent<UIHolderObjectBase>();
|
||||
CollectUXHotkeys();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal void UnBindHotKeys()
|
||||
{
|
||||
if (hotButtons == null) return;
|
||||
for (int i = 0; i < hotButtons.Length; i++)
|
||||
{
|
||||
if (hotButtons[i] is IHotkeyTrigger trigger)
|
||||
{
|
||||
trigger.UnBindHotKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdcda7a93f3c4639a0cb68dd00509bb1
|
||||
timeCreated: 1758683821
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
#if INPUTSYSTEM_SUPPORT
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
@ -8,11 +8,8 @@ namespace UnityEngine.UI
|
||||
public sealed class HotkeyComponent : MonoBehaviour, IHotkeyTrigger
|
||||
{
|
||||
[SerializeField] private Component _component;
|
||||
|
||||
[SerializeField] private InputActionReference _hotkeyAction;
|
||||
|
||||
[SerializeField] private EHotkeyPressType _hotkeyPressType;
|
||||
|
||||
[SerializeField] private EHotkeyPressType _hotkeyPressType = EHotkeyPressType.Performed;
|
||||
|
||||
public InputActionReference HotkeyAction
|
||||
{
|
||||
@ -26,12 +23,66 @@ namespace UnityEngine.UI
|
||||
set => _hotkeyPressType = value;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
AutoAssignTarget();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
AutoAssignTarget();
|
||||
((IHotkeyTrigger)this).BindHotKey();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
((IHotkeyTrigger)this).UnBindHotKey();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
((IHotkeyTrigger)this).UnBindHotKey();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
AutoAssignTarget();
|
||||
}
|
||||
#endif
|
||||
|
||||
void IHotkeyTrigger.HotkeyActionTrigger()
|
||||
{
|
||||
if (_component is ISubmitHandler submitHandler)
|
||||
if (!isActiveAndEnabled || _component == null)
|
||||
{
|
||||
submitHandler.OnSubmit(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_component is ISubmitHandler)
|
||||
{
|
||||
ExecuteEvents.Execute(
|
||||
_component.gameObject,
|
||||
new BaseEventData(EventSystem.current),
|
||||
ExecuteEvents.submitHandler
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"{nameof(HotkeyComponent)} target must implement {nameof(ISubmitHandler)}: {_component.name}", this);
|
||||
}
|
||||
|
||||
private void AutoAssignTarget()
|
||||
{
|
||||
if (_component != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryGetComponent(typeof(ISubmitHandler), out Component submitHandler))
|
||||
{
|
||||
_component = submitHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
#if INPUTSYSTEM_SUPPORT
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using AlicizaX.UI.Runtime;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.UI
|
||||
{
|
||||
@ -16,395 +14,638 @@ namespace UnityEngine.UI
|
||||
Performed = 1
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
internal struct HotkeyRegistration
|
||||
internal readonly struct HotkeyRegistration
|
||||
{
|
||||
public IHotkeyTrigger button;
|
||||
public EHotkeyPressType pressType;
|
||||
public readonly IHotkeyTrigger Trigger;
|
||||
public readonly EHotkeyPressType PressType;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public HotkeyRegistration(IHotkeyTrigger btn, EHotkeyPressType pressType)
|
||||
public HotkeyRegistration(IHotkeyTrigger trigger, EHotkeyPressType pressType)
|
||||
{
|
||||
this.button = btn;
|
||||
this.pressType = pressType;
|
||||
Trigger = trigger;
|
||||
PressType = pressType;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct HandlerInfo
|
||||
internal sealed class HotkeyScope
|
||||
{
|
||||
public Action<InputAction.CallbackContext> handler;
|
||||
public InputActionReference action;
|
||||
public HotkeyScope(UIHolderObjectBase holder)
|
||||
{
|
||||
Holder = holder;
|
||||
HierarchyDepth = GetHierarchyDepth(holder.transform);
|
||||
BlocksLowerScopes = FindParentHolder(holder) == null;
|
||||
}
|
||||
|
||||
public HandlerInfo(Action<InputAction.CallbackContext> handler, InputActionReference action)
|
||||
public readonly UIHolderObjectBase Holder;
|
||||
public readonly int HierarchyDepth;
|
||||
public readonly bool BlocksLowerScopes;
|
||||
public readonly Dictionary<string, List<HotkeyRegistration>> RegistrationsByAction = new(StringComparer.Ordinal);
|
||||
|
||||
public bool LifecycleActive;
|
||||
public ulong ActivationSerial;
|
||||
|
||||
public Action OnBeforeShow;
|
||||
public Action OnBeforeClosed;
|
||||
public Action OnDestroy;
|
||||
|
||||
private Canvas _canvas;
|
||||
|
||||
public Canvas Canvas
|
||||
{
|
||||
this.handler = handler;
|
||||
this.action = action;
|
||||
get
|
||||
{
|
||||
if (_canvas == null && Holder != null)
|
||||
{
|
||||
_canvas = Holder.GetComponent<Canvas>();
|
||||
}
|
||||
|
||||
return _canvas;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetHierarchyDepth(Transform current)
|
||||
{
|
||||
int depth = 0;
|
||||
while (current != null)
|
||||
{
|
||||
depth++;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static UIHolderObjectBase FindParentHolder(UIHolderObjectBase holder)
|
||||
{
|
||||
if (holder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Transform current = holder.transform.parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current.TryGetComponent<UIHolderObjectBase>(out var parentHolder))
|
||||
{
|
||||
return parentHolder;
|
||||
}
|
||||
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ActionRegistrationBucket
|
||||
{
|
||||
public InputActionReference ActionReference;
|
||||
public Action<InputAction.CallbackContext> StartedHandler;
|
||||
public Action<InputAction.CallbackContext> PerformedHandler;
|
||||
public int StartedCount;
|
||||
public int PerformedCount;
|
||||
|
||||
public int TotalCount => StartedCount + PerformedCount;
|
||||
}
|
||||
|
||||
internal readonly struct TriggerRegistration
|
||||
{
|
||||
public readonly string ActionId;
|
||||
public readonly UIHolderObjectBase Holder;
|
||||
public readonly EHotkeyPressType PressType;
|
||||
|
||||
public TriggerRegistration(string actionId, UIHolderObjectBase holder, EHotkeyPressType pressType)
|
||||
{
|
||||
ActionId = actionId;
|
||||
Holder = holder;
|
||||
PressType = pressType;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class UXHotkeyRegisterManager
|
||||
{
|
||||
private const int INITIAL_CAPACITY = 32;
|
||||
private const int MAX_REGISTRATIONS_PER_ACTION = 16;
|
||||
private static readonly Dictionary<string, ActionRegistrationBucket> _actions = new(StringComparer.Ordinal);
|
||||
private static readonly Dictionary<IHotkeyTrigger, TriggerRegistration> _triggerMap = new();
|
||||
private static readonly Dictionary<UIHolderObjectBase, HotkeyScope> _scopes = new();
|
||||
private static readonly List<HotkeyScope> _leafScopes = new();
|
||||
private static readonly HashSet<UIHolderObjectBase> _ancestorHolders = new();
|
||||
|
||||
private static HotkeyRegistration[][] _registrationPool;
|
||||
private static int[] _registrationCounts;
|
||||
private static string[] _actionIds;
|
||||
private static int _actionCount;
|
||||
private static int _actionCapacity;
|
||||
|
||||
private static HandlerInfo[] _handlers;
|
||||
private static InputActionReference[] _actionRefs;
|
||||
|
||||
private static IHotkeyTrigger[] _buttons;
|
||||
private static int[] _buttonToActionIndex;
|
||||
private static int _buttonCount;
|
||||
private static int _buttonCapacity;
|
||||
|
||||
private static Action<InputAction.CallbackContext>[] _cachedHandlers;
|
||||
private static int _cachedHandlerCount;
|
||||
|
||||
static UXHotkeyRegisterManager()
|
||||
{
|
||||
_actionCapacity = INITIAL_CAPACITY;
|
||||
_registrationPool = new HotkeyRegistration[_actionCapacity][];
|
||||
_registrationCounts = new int[_actionCapacity];
|
||||
_actionIds = new string[_actionCapacity];
|
||||
_handlers = new HandlerInfo[_actionCapacity];
|
||||
_actionRefs = new InputActionReference[_actionCapacity];
|
||||
|
||||
_buttonCapacity = 64;
|
||||
_buttons = new IHotkeyTrigger[_buttonCapacity];
|
||||
_buttonToActionIndex = new int[_buttonCapacity];
|
||||
|
||||
_cachedHandlers = new Action<InputAction.CallbackContext>[INITIAL_CAPACITY];
|
||||
|
||||
for (int i = 0; i < _actionCapacity; i++)
|
||||
{
|
||||
_registrationPool[i] = new HotkeyRegistration[MAX_REGISTRATIONS_PER_ACTION];
|
||||
_buttonToActionIndex[i] = -1;
|
||||
}
|
||||
}
|
||||
private static ulong _serialCounter;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.Callbacks.DidReloadScripts]
|
||||
internal static void ClearHotkeyRegistry()
|
||||
{
|
||||
for (int i = 0; i < _buttonCount; i++)
|
||||
IHotkeyTrigger[] triggers = new IHotkeyTrigger[_triggerMap.Count];
|
||||
int index = 0;
|
||||
foreach (var kvp in _triggerMap)
|
||||
{
|
||||
if (_buttons[i] != null)
|
||||
{
|
||||
UnregisterHotkey(_buttons[i]);
|
||||
}
|
||||
triggers[index++] = kvp.Key;
|
||||
}
|
||||
|
||||
Array.Clear(_registrationCounts, 0, _actionCount);
|
||||
Array.Clear(_actionIds, 0, _actionCount);
|
||||
Array.Clear(_handlers, 0, _actionCount);
|
||||
Array.Clear(_actionRefs, 0, _actionCount);
|
||||
Array.Clear(_buttons, 0, _buttonCount);
|
||||
Array.Clear(_buttonToActionIndex, 0, _buttonCount);
|
||||
for (int i = 0; i < triggers.Length; i++)
|
||||
{
|
||||
UnregisterHotkey(triggers[i]);
|
||||
}
|
||||
|
||||
_actionCount = 0;
|
||||
_buttonCount = 0;
|
||||
_cachedHandlerCount = 0;
|
||||
_actions.Clear();
|
||||
_triggerMap.Clear();
|
||||
_scopes.Clear();
|
||||
_leafScopes.Clear();
|
||||
_ancestorHolders.Clear();
|
||||
_serialCounter = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void RegisterHotkey(IHotkeyTrigger button, InputActionReference action, EHotkeyPressType pressType)
|
||||
internal static void RegisterHotkey(IHotkeyTrigger trigger, UIHolderObjectBase holder, InputActionReference action, EHotkeyPressType pressType)
|
||||
{
|
||||
if (trigger == null || holder == null || action == null || action.action == null)
|
||||
{
|
||||
if (action == null || action.action == null || button == null)
|
||||
return;
|
||||
}
|
||||
|
||||
UnregisterHotkey(trigger);
|
||||
|
||||
string actionId = action.action.id.ToString();
|
||||
int actionIndex = FindOrCreateActionIndex(actionId);
|
||||
HotkeyScope scope = GetOrCreateScope(holder);
|
||||
ActionRegistrationBucket bucket = GetOrCreateBucket(actionId, action);
|
||||
HotkeyRegistration registration = new HotkeyRegistration(trigger, pressType);
|
||||
|
||||
ref int count = ref _registrationCounts[actionIndex];
|
||||
if (count >= MAX_REGISTRATIONS_PER_ACTION)
|
||||
AdjustBucketSubscription(bucket, pressType, true);
|
||||
AddScopeRegistration(scope, actionId, registration);
|
||||
|
||||
if (scope.LifecycleActive)
|
||||
{
|
||||
scope.ActivationSerial = ++_serialCounter;
|
||||
}
|
||||
|
||||
_triggerMap[trigger] = new TriggerRegistration(actionId, holder, pressType);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void UnregisterHotkey(IHotkeyTrigger trigger)
|
||||
{
|
||||
if (trigger == null || !_triggerMap.TryGetValue(trigger, out var triggerRegistration))
|
||||
{
|
||||
Debug.LogWarning($"Max registrations reached for action: {actionId}");
|
||||
return;
|
||||
}
|
||||
|
||||
_registrationPool[actionIndex][count] = new HotkeyRegistration(button, pressType);
|
||||
count++;
|
||||
|
||||
int buttonIndex = FindOrCreateButtonIndex(button);
|
||||
_buttonToActionIndex[buttonIndex] = actionIndex;
|
||||
|
||||
if (count == 1)
|
||||
if (_actions.TryGetValue(triggerRegistration.ActionId, out var bucket))
|
||||
{
|
||||
Action<InputAction.CallbackContext> handler = GetOrCreateHandler(actionIndex);
|
||||
_handlers[actionIndex] = new HandlerInfo(handler, action);
|
||||
_actionRefs[actionIndex] = action;
|
||||
RemoveActionRegistration(bucket, triggerRegistration.PressType, triggerRegistration.ActionId);
|
||||
}
|
||||
|
||||
if (_scopes.TryGetValue(triggerRegistration.Holder, out var scope))
|
||||
{
|
||||
RemoveScopeRegistration(scope, triggerRegistration.ActionId, trigger);
|
||||
ReleaseScopeIfEmpty(scope);
|
||||
}
|
||||
|
||||
_triggerMap.Remove(trigger);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ActionRegistrationBucket GetOrCreateBucket(string actionId, InputActionReference action)
|
||||
{
|
||||
if (_actions.TryGetValue(actionId, out var bucket))
|
||||
{
|
||||
return bucket;
|
||||
}
|
||||
|
||||
bucket = new ActionRegistrationBucket
|
||||
{
|
||||
ActionReference = action,
|
||||
StartedHandler = _ => Dispatch(actionId, EHotkeyPressType.Started),
|
||||
PerformedHandler = _ => Dispatch(actionId, EHotkeyPressType.Performed)
|
||||
};
|
||||
_actions[actionId] = bucket;
|
||||
return bucket;
|
||||
}
|
||||
|
||||
private static HotkeyScope GetOrCreateScope(UIHolderObjectBase holder)
|
||||
{
|
||||
if (_scopes.TryGetValue(holder, out var scope))
|
||||
{
|
||||
return scope;
|
||||
}
|
||||
|
||||
scope = new HotkeyScope(holder)
|
||||
{
|
||||
LifecycleActive = IsHolderVisible(holder),
|
||||
ActivationSerial = ++_serialCounter
|
||||
};
|
||||
|
||||
scope.OnBeforeShow = () => ActivateScope(holder);
|
||||
scope.OnBeforeClosed = () => DeactivateScope(holder);
|
||||
scope.OnDestroy = () => DestroyScope(holder);
|
||||
|
||||
holder.OnWindowBeforeShowEvent += scope.OnBeforeShow;
|
||||
holder.OnWindowBeforeClosedEvent += scope.OnBeforeClosed;
|
||||
holder.OnWindowDestroyEvent += scope.OnDestroy;
|
||||
|
||||
_scopes[holder] = scope;
|
||||
return scope;
|
||||
}
|
||||
|
||||
private static void ActivateScope(UIHolderObjectBase holder)
|
||||
{
|
||||
if (_scopes.TryGetValue(holder, out var scope))
|
||||
{
|
||||
scope.LifecycleActive = true;
|
||||
scope.ActivationSerial = ++_serialCounter;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeactivateScope(UIHolderObjectBase holder)
|
||||
{
|
||||
if (_scopes.TryGetValue(holder, out var scope))
|
||||
{
|
||||
scope.LifecycleActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DestroyScope(UIHolderObjectBase holder)
|
||||
{
|
||||
if (holder == null || !_scopes.TryGetValue(holder, out var scope))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<IHotkeyTrigger> triggers = null;
|
||||
foreach (var pair in scope.RegistrationsByAction)
|
||||
{
|
||||
List<HotkeyRegistration> registrations = pair.Value;
|
||||
for (int i = 0; i < registrations.Count; i++)
|
||||
{
|
||||
triggers ??= new List<IHotkeyTrigger>(registrations.Count);
|
||||
triggers.Add(registrations[i].Trigger);
|
||||
}
|
||||
}
|
||||
|
||||
if (triggers != null)
|
||||
{
|
||||
for (int i = 0; i < triggers.Count; i++)
|
||||
{
|
||||
UnregisterHotkey(triggers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
DetachScope(scope);
|
||||
}
|
||||
|
||||
private static void DetachScope(HotkeyScope scope)
|
||||
{
|
||||
if (scope == null || scope.Holder == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scope.Holder.OnWindowBeforeShowEvent -= scope.OnBeforeShow;
|
||||
scope.Holder.OnWindowBeforeClosedEvent -= scope.OnBeforeClosed;
|
||||
scope.Holder.OnWindowDestroyEvent -= scope.OnDestroy;
|
||||
_scopes.Remove(scope.Holder);
|
||||
}
|
||||
|
||||
private static void ReleaseScopeIfEmpty(HotkeyScope scope)
|
||||
{
|
||||
if (scope != null && scope.RegistrationsByAction.Count == 0)
|
||||
{
|
||||
DetachScope(scope);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddScopeRegistration(HotkeyScope scope, string actionId, HotkeyRegistration registration)
|
||||
{
|
||||
if (!scope.RegistrationsByAction.TryGetValue(actionId, out var registrations))
|
||||
{
|
||||
registrations = new List<HotkeyRegistration>();
|
||||
scope.RegistrationsByAction[actionId] = registrations;
|
||||
}
|
||||
|
||||
registrations.Add(registration);
|
||||
}
|
||||
|
||||
private static void RemoveScopeRegistration(HotkeyScope scope, string actionId, IHotkeyTrigger trigger)
|
||||
{
|
||||
if (!scope.RegistrationsByAction.TryGetValue(actionId, out var registrations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = registrations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (ReferenceEquals(registrations[i].Trigger, trigger))
|
||||
{
|
||||
registrations.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (registrations.Count == 0)
|
||||
{
|
||||
scope.RegistrationsByAction.Remove(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveActionRegistration(ActionRegistrationBucket bucket, EHotkeyPressType pressType, string actionId)
|
||||
{
|
||||
AdjustBucketSubscription(bucket, pressType, false);
|
||||
if (bucket.TotalCount == 0)
|
||||
{
|
||||
_actions.Remove(actionId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AdjustBucketSubscription(ActionRegistrationBucket bucket, EHotkeyPressType pressType, bool add)
|
||||
{
|
||||
InputAction inputAction = bucket.ActionReference != null ? bucket.ActionReference.action : null;
|
||||
if (inputAction == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pressType)
|
||||
{
|
||||
case EHotkeyPressType.Started:
|
||||
action.action.started += handler;
|
||||
if (add)
|
||||
{
|
||||
if (bucket.StartedCount == 0)
|
||||
{
|
||||
inputAction.started += bucket.StartedHandler;
|
||||
}
|
||||
|
||||
bucket.StartedCount++;
|
||||
}
|
||||
else if (bucket.StartedCount > 0)
|
||||
{
|
||||
bucket.StartedCount--;
|
||||
if (bucket.StartedCount == 0)
|
||||
{
|
||||
inputAction.started -= bucket.StartedHandler;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case EHotkeyPressType.Performed:
|
||||
action.action.performed += handler;
|
||||
if (add)
|
||||
{
|
||||
if (bucket.PerformedCount == 0)
|
||||
{
|
||||
inputAction.performed += bucket.PerformedHandler;
|
||||
}
|
||||
|
||||
bucket.PerformedCount++;
|
||||
}
|
||||
else if (bucket.PerformedCount > 0)
|
||||
{
|
||||
bucket.PerformedCount--;
|
||||
if (bucket.PerformedCount == 0)
|
||||
{
|
||||
inputAction.performed -= bucket.PerformedHandler;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
action.action.Enable();
|
||||
if (bucket.TotalCount > 0)
|
||||
{
|
||||
inputAction.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
inputAction.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnregisterHotkey(IHotkeyTrigger button)
|
||||
private static void Dispatch(string actionId, EHotkeyPressType pressType)
|
||||
{
|
||||
HotkeyScope[] leafScopes = GetLeafScopes();
|
||||
if (leafScopes.Length == 0)
|
||||
{
|
||||
if (button == null)
|
||||
return;
|
||||
|
||||
int buttonIndex = FindButtonIndex(button);
|
||||
if (buttonIndex < 0)
|
||||
return;
|
||||
|
||||
int actionIndex = _buttonToActionIndex[buttonIndex];
|
||||
if (actionIndex < 0)
|
||||
return;
|
||||
|
||||
ref int count = ref _registrationCounts[actionIndex];
|
||||
HotkeyRegistration[] registrations = _registrationPool[actionIndex];
|
||||
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (ReferenceEquals(registrations[i].button, button))
|
||||
{
|
||||
EHotkeyPressType pressType = registrations[i].pressType;
|
||||
|
||||
registrations[i] = registrations[count - 1];
|
||||
registrations[count - 1] = default;
|
||||
count--;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
ref HandlerInfo handlerInfo = ref _handlers[actionIndex];
|
||||
InputActionReference actionRef = _actionRefs[actionIndex];
|
||||
|
||||
if (actionRef != null && actionRef.action != null)
|
||||
{
|
||||
switch (pressType)
|
||||
{
|
||||
case EHotkeyPressType.Started:
|
||||
actionRef.action.started -= handlerInfo.handler;
|
||||
break;
|
||||
case EHotkeyPressType.Performed:
|
||||
actionRef.action.performed -= handlerInfo.handler;
|
||||
break;
|
||||
}
|
||||
|
||||
actionRef.action.Disable();
|
||||
TryDispatchToScopeChain(leafScopes[0], actionId, pressType);
|
||||
}
|
||||
|
||||
handlerInfo = default;
|
||||
_actionRefs[actionIndex] = null;
|
||||
_actionIds[actionIndex] = null;
|
||||
private static bool TryDispatchToScopeChain(HotkeyScope leafScope, string actionId, EHotkeyPressType pressType)
|
||||
{
|
||||
UIHolderObjectBase currentHolder = leafScope.Holder;
|
||||
while (currentHolder != null)
|
||||
{
|
||||
if (_scopes.TryGetValue(currentHolder, out var scope)
|
||||
&& TryGetLatestRegistration(scope, actionId, pressType, out var registration))
|
||||
{
|
||||
registration.Trigger?.HotkeyActionTrigger();
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
currentHolder = FindParentHolder(currentHolder);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetLatestRegistration(HotkeyScope scope, string actionId, EHotkeyPressType pressType, out HotkeyRegistration registration)
|
||||
{
|
||||
if (scope.RegistrationsByAction.TryGetValue(actionId, out var registrations))
|
||||
{
|
||||
for (int i = registrations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
HotkeyRegistration candidate = registrations[i];
|
||||
if (candidate.PressType == pressType && candidate.Trigger != null)
|
||||
{
|
||||
registration = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_buttons[buttonIndex] = null;
|
||||
_buttonToActionIndex[buttonIndex] = -1;
|
||||
registration = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Action<InputAction.CallbackContext> GetOrCreateHandler(int actionIndex)
|
||||
private static HotkeyScope[] GetLeafScopes()
|
||||
{
|
||||
if (actionIndex < _cachedHandlers.Length && _cachedHandlers[actionIndex] != null)
|
||||
_leafScopes.Clear();
|
||||
_ancestorHolders.Clear();
|
||||
|
||||
foreach (var scope in _scopes.Values)
|
||||
{
|
||||
return _cachedHandlers[actionIndex];
|
||||
if (!IsScopeActive(scope))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (actionIndex >= _cachedHandlers.Length)
|
||||
UIHolderObjectBase parentHolder = FindParentHolder(scope.Holder);
|
||||
while (parentHolder != null)
|
||||
{
|
||||
Array.Resize(ref _cachedHandlers, actionIndex + 1);
|
||||
}
|
||||
|
||||
int capturedIndex = actionIndex;
|
||||
_cachedHandlers[actionIndex] = ctx => OnHotkeyTriggered(capturedIndex);
|
||||
return _cachedHandlers[actionIndex];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void OnHotkeyTriggered(int actionIndex)
|
||||
{
|
||||
int count = _registrationCounts[actionIndex];
|
||||
if (count > 0)
|
||||
{
|
||||
ref HotkeyRegistration registration = ref _registrationPool[actionIndex][count - 1];
|
||||
registration.button?.HotkeyActionTrigger();
|
||||
_ancestorHolders.Add(parentHolder);
|
||||
parentHolder = FindParentHolder(parentHolder);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int FindOrCreateActionIndex(string actionId)
|
||||
foreach (var scope in _scopes.Values)
|
||||
{
|
||||
for (int i = 0; i < _actionCount; i++)
|
||||
if (IsScopeActive(scope) && !_ancestorHolders.Contains(scope.Holder))
|
||||
{
|
||||
if (_actionIds[i] == actionId)
|
||||
return i;
|
||||
}
|
||||
|
||||
if (_actionCount >= _actionCapacity)
|
||||
{
|
||||
ExpandActionCapacity();
|
||||
}
|
||||
|
||||
int index = _actionCount++;
|
||||
_actionIds[index] = actionId;
|
||||
return index;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int FindOrCreateButtonIndex(IHotkeyTrigger button)
|
||||
{
|
||||
for (int i = 0; i < _buttonCount; i++)
|
||||
{
|
||||
if (ReferenceEquals(_buttons[i], button))
|
||||
return i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _buttonCapacity; i++)
|
||||
{
|
||||
if (_buttons[i] == null)
|
||||
{
|
||||
_buttons[i] = button;
|
||||
if (i >= _buttonCount)
|
||||
_buttonCount = i + 1;
|
||||
return i;
|
||||
_leafScopes.Add(scope);
|
||||
}
|
||||
}
|
||||
|
||||
if (_buttonCount >= _buttonCapacity)
|
||||
{
|
||||
ExpandButtonCapacity();
|
||||
_leafScopes.Sort(CompareScopePriority);
|
||||
return _leafScopes.ToArray();
|
||||
}
|
||||
|
||||
int index = _buttonCount++;
|
||||
_buttons[index] = button;
|
||||
return index;
|
||||
private static bool IsScopeActive(HotkeyScope scope)
|
||||
{
|
||||
if (scope == null || !scope.LifecycleActive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int FindButtonIndex(IHotkeyTrigger button)
|
||||
UIHolderObjectBase holder = scope.Holder;
|
||||
if (holder == null || !holder.IsValid())
|
||||
{
|
||||
for (int i = 0; i < _buttonCount; i++)
|
||||
{
|
||||
if (ReferenceEquals(_buttons[i], button))
|
||||
return i;
|
||||
return false;
|
||||
}
|
||||
|
||||
return -1;
|
||||
if (!holder.gameObject.activeInHierarchy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ExpandActionCapacity()
|
||||
{
|
||||
int newCapacity = _actionCapacity * 2;
|
||||
Array.Resize(ref _registrationPool, newCapacity);
|
||||
Array.Resize(ref _registrationCounts, newCapacity);
|
||||
Array.Resize(ref _actionIds, newCapacity);
|
||||
Array.Resize(ref _handlers, newCapacity);
|
||||
Array.Resize(ref _actionRefs, newCapacity);
|
||||
|
||||
for (int i = _actionCapacity; i < newCapacity; i++)
|
||||
{
|
||||
_registrationPool[i] = new HotkeyRegistration[MAX_REGISTRATIONS_PER_ACTION];
|
||||
Canvas canvas = scope.Canvas;
|
||||
return canvas != null && canvas.gameObject.layer == UIComponent.UIShowLayer;
|
||||
}
|
||||
|
||||
_actionCapacity = newCapacity;
|
||||
private static bool IsHolderVisible(UIHolderObjectBase holder)
|
||||
{
|
||||
if (holder == null || !holder.gameObject.activeInHierarchy)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ExpandButtonCapacity()
|
||||
{
|
||||
int newCapacity = _buttonCapacity * 2;
|
||||
Array.Resize(ref _buttons, newCapacity);
|
||||
Array.Resize(ref _buttonToActionIndex, newCapacity);
|
||||
|
||||
for (int i = _buttonCapacity; i < newCapacity; i++)
|
||||
{
|
||||
_buttonToActionIndex[i] = -1;
|
||||
Canvas canvas = holder.GetComponent<Canvas>();
|
||||
return canvas != null && canvas.gameObject.layer == UIComponent.UIShowLayer;
|
||||
}
|
||||
|
||||
_buttonCapacity = newCapacity;
|
||||
private static int CompareScopePriority(HotkeyScope left, HotkeyScope right)
|
||||
{
|
||||
int leftDepth = left.Canvas != null ? left.Canvas.sortingOrder : int.MinValue;
|
||||
int rightDepth = right.Canvas != null ? right.Canvas.sortingOrder : int.MinValue;
|
||||
int depthCompare = rightDepth.CompareTo(leftDepth);
|
||||
if (depthCompare != 0)
|
||||
{
|
||||
return depthCompare;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RegisterHotkeyBatch(Span<IHotkeyTrigger> buttons, InputActionReference action, EHotkeyPressType pressType)
|
||||
int hierarchyCompare = right.HierarchyDepth.CompareTo(left.HierarchyDepth);
|
||||
if (hierarchyCompare != 0)
|
||||
{
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
RegisterHotkey(buttons[i], action, pressType);
|
||||
}
|
||||
return hierarchyCompare;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnregisterHotkeyBatch(Span<IHotkeyTrigger> buttons)
|
||||
{
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
UnregisterHotkey(buttons[i]);
|
||||
return right.ActivationSerial.CompareTo(left.ActivationSerial);
|
||||
}
|
||||
|
||||
private static UIHolderObjectBase FindParentHolder(UIHolderObjectBase holder)
|
||||
{
|
||||
if (holder == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Transform current = holder.transform.parent;
|
||||
while (current != null)
|
||||
{
|
||||
if (current.TryGetComponent<UIHolderObjectBase>(out var parentHolder))
|
||||
{
|
||||
return parentHolder;
|
||||
}
|
||||
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static string GetDebugInfo()
|
||||
{
|
||||
return $"Actions: {_actionCount}/{_actionCapacity}, Buttons: {_buttonCount}/{_buttonCapacity}";
|
||||
return $"Actions: {_actions.Count}, Triggers: {_triggerMap.Count}, Scopes: {_scopes.Count}";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnityEngine.UI
|
||||
{
|
||||
public static class UXHotkeyHotkeyExtension
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void BindHotKey(this IHotkeyTrigger button)
|
||||
public static void BindHotKey(this IHotkeyTrigger trigger)
|
||||
{
|
||||
if (button?.HotkeyAction != null)
|
||||
if (trigger?.HotkeyAction == null)
|
||||
{
|
||||
UXHotkeyRegisterManager.RegisterHotkey(
|
||||
button,
|
||||
button.HotkeyAction,
|
||||
button.HotkeyPressType
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger is not Component component)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UIHolderObjectBase holder = component.GetComponentInParent<UIHolderObjectBase>(true);
|
||||
if (holder == null)
|
||||
{
|
||||
Debug.LogWarning($"{nameof(HotkeyComponent)} could not find a {nameof(UIHolderObjectBase)} owner.", component);
|
||||
return;
|
||||
}
|
||||
|
||||
UXHotkeyRegisterManager.RegisterHotkey(trigger, holder, trigger.HotkeyAction, trigger.HotkeyPressType);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnBindHotKey(this IHotkeyTrigger trigger)
|
||||
{
|
||||
if (trigger?.HotkeyAction != null)
|
||||
{
|
||||
UXHotkeyRegisterManager.UnregisterHotkey(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnBindHotKey(this IHotkeyTrigger button)
|
||||
public static void BindHotKeyBatch(this IHotkeyTrigger[] triggers)
|
||||
{
|
||||
if (button?.HotkeyAction != null)
|
||||
if (triggers == null)
|
||||
{
|
||||
UXHotkeyRegisterManager.UnregisterHotkey(button);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < triggers.Length; i++)
|
||||
{
|
||||
triggers[i]?.BindHotKey();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void BindHotKeyBatch(this IHotkeyTrigger[] buttons)
|
||||
public static void UnBindHotKeyBatch(this IHotkeyTrigger[] triggers)
|
||||
{
|
||||
if (buttons == null) return;
|
||||
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
if (triggers == null)
|
||||
{
|
||||
buttons[i]?.BindHotKey();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UnBindHotKeyBatch(this IHotkeyTrigger[] buttons)
|
||||
for (int i = 0; i < triggers.Length; i++)
|
||||
{
|
||||
if (buttons == null) return;
|
||||
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
buttons[i]?.UnBindHotKey();
|
||||
triggers[i]?.UnBindHotKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user