From d8fd91d32c4350b915f08426246ae0fd72867384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Mon, 13 Oct 2025 20:20:01 +0800 Subject: [PATCH] 1 --- Editor/UX/Hotkey.meta | 3 + .../UX/Hotkey/HotkeyBindComponentInspector.cs | 111 ++++++ .../HotkeyBindComponentInspector.cs.meta | 3 + Editor/UX/Hotkey/HotkeyInspector.cs | 45 +++ Editor/UX/Hotkey/HotkeyInspector.cs.meta | 3 + Editor/UX/UXButtonEditor.cs | 25 +- Editor/UX/UXGroupInspector.cs | 50 +++ Editor/UX/UXGroupInspector.cs.meta | 3 + Runtime/UXComponent/Button/UXButton.cs | 376 ++++++------------ Runtime/UXComponent/Group/UXGroup.cs | 157 +++----- .../UXComponent/Hotkey/HotkeyBindComponent.cs | 30 +- Runtime/UXComponent/Hotkey/UXHotkey.cs | 170 +------- Runtime/UXComponent/Hotkey/UXHotkey.cs.meta | 4 +- .../Hotkey/UXHotkeyRegisterManager.cs | 174 ++++++++ .../Hotkey/UXHotkeyRegisterManager.cs.meta | 3 + 15 files changed, 592 insertions(+), 565 deletions(-) create mode 100644 Editor/UX/Hotkey.meta create mode 100644 Editor/UX/Hotkey/HotkeyBindComponentInspector.cs create mode 100644 Editor/UX/Hotkey/HotkeyBindComponentInspector.cs.meta create mode 100644 Editor/UX/Hotkey/HotkeyInspector.cs create mode 100644 Editor/UX/Hotkey/HotkeyInspector.cs.meta create mode 100644 Editor/UX/UXGroupInspector.cs create mode 100644 Editor/UX/UXGroupInspector.cs.meta create mode 100644 Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs create mode 100644 Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs.meta diff --git a/Editor/UX/Hotkey.meta b/Editor/UX/Hotkey.meta new file mode 100644 index 0000000..f56e896 --- /dev/null +++ b/Editor/UX/Hotkey.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: be711d9c48d440c4afccffac091ccc7d +timeCreated: 1760340542 \ No newline at end of file diff --git a/Editor/UX/Hotkey/HotkeyBindComponentInspector.cs b/Editor/UX/Hotkey/HotkeyBindComponentInspector.cs new file mode 100644 index 0000000..b653957 --- /dev/null +++ b/Editor/UX/Hotkey/HotkeyBindComponentInspector.cs @@ -0,0 +1,111 @@ +#if INPUTSYSTEM_SUPPORT +using System.Collections.Generic; +using System.Reflection; +using AlicizaX.Editor; +using AlicizaX.UI.Extension; +using AlicizaX.UI.Extension.UXComponent.Hotkey; +using AlicizaX.UI.Runtime; +using UnityEditor; +using UnityEngine; + +namespace UnityEngine.UI.Hotkey +{ + [CustomEditor(typeof(HotkeyBindComponent))] + public class HotkeyBindComponentInspector : GameFrameworkInspector + { + private SerializedProperty hotButtonsProp; + private HotkeyBindComponent _target; + + private void OnEnable() + { + _target = (HotkeyBindComponent)target; + hotButtonsProp = serializedObject.FindProperty("hotButtons"); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + // 🔸 检查是否缺少 UIHolderObjectBase + var holder = _target.GetComponent(); + 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(); + } + + /// + /// 自定义展开显示 hotButtons 列表 + /// + private void DrawHotButtonListAlwaysExpanded(SerializedProperty listProp) + { + EditorGUILayout.LabelField("Hot Buttons", EditorStyles.boldLabel); + + if (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 uxHotkey = element.objectReferenceValue as UXHotkey; + string name = uxHotkey != null ? uxHotkey.name : "Null"; + EditorGUILayout.ObjectField($"[{i}] {name}", element.objectReferenceValue, typeof(UXHotkey), true); + } + + EditorGUI.indentLevel--; + } + + /// + /// 查找所有子物体(包含隐藏)并绑定 UXHotkey + /// + private void FindAllUXHotkeys() + { + Undo.RecordObject(_target, "Scan UXHotkey"); + + var uxHotkeys = _target.GetComponentsInChildren(true); + List valiedHotkeys = new List(); + foreach (var item in uxHotkeys) + { + var field = item.GetType() + .GetField("_hotKeyRefrence", BindingFlags.NonPublic | BindingFlags.Instance); + var hotRefrenceObject = field.GetValue(item); + if (hotRefrenceObject != null) valiedHotkeys.Add(item); + } + + _target.GetType() + .GetField("hotButtons", BindingFlags.NonPublic | BindingFlags.Instance) + ?.SetValue(_target, valiedHotkeys.ToArray()); + + EditorUtility.SetDirty(_target); + serializedObject.Update(); + + Debug.Log($"[HotkeyBindComponent] 已找到 {uxHotkeys.Length} 个 UXHotkey 组件并绑定。"); + } + } +} + +#endif diff --git a/Editor/UX/Hotkey/HotkeyBindComponentInspector.cs.meta b/Editor/UX/Hotkey/HotkeyBindComponentInspector.cs.meta new file mode 100644 index 0000000..d68aaf1 --- /dev/null +++ b/Editor/UX/Hotkey/HotkeyBindComponentInspector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f9779e310e684e9497bdac4f4ac3983e +timeCreated: 1760340548 \ No newline at end of file diff --git a/Editor/UX/Hotkey/HotkeyInspector.cs b/Editor/UX/Hotkey/HotkeyInspector.cs new file mode 100644 index 0000000..51a91b7 --- /dev/null +++ b/Editor/UX/Hotkey/HotkeyInspector.cs @@ -0,0 +1,45 @@ +#if INPUTSYSTEM_SUPPORT +using AlicizaX.Editor; +using AlicizaX.UI.Extension; +using UnityEditor; + +namespace UnityEngine.UI.Hotkey +{ + [CustomEditor(typeof(UXHotkey))] + public class HotkeyInspector : GameFrameworkInspector + { + private UXHotkey _target; + private SerializedProperty _button; + private SerializedProperty _hotKeyRefrence; + private SerializedProperty _hotkeyPressType; + + private void OnEnable() + { + _target = (UXHotkey)target; + _button = serializedObject.FindProperty("_button"); + _hotKeyRefrence = serializedObject.FindProperty("_hotKeyRefrence"); + _hotkeyPressType = serializedObject.FindProperty("_hotkeyPressType"); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + var holder = _target.GetComponent(); + if (holder == null) + { + EditorGUILayout.HelpBox( + "⚠ 当前对象缺少 UXButton 组件", + MessageType.Error + ); + } + + EditorGUILayout.PropertyField(_button); + EditorGUILayout.PropertyField(_hotKeyRefrence); + EditorGUILayout.PropertyField(_hotkeyPressType); + serializedObject.ApplyModifiedProperties(); + } + } +} + +#endif diff --git a/Editor/UX/Hotkey/HotkeyInspector.cs.meta b/Editor/UX/Hotkey/HotkeyInspector.cs.meta new file mode 100644 index 0000000..8a4c8a0 --- /dev/null +++ b/Editor/UX/Hotkey/HotkeyInspector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 87a10dc3a1544ec586ff27809213a127 +timeCreated: 1760341800 \ No newline at end of file diff --git a/Editor/UX/UXButtonEditor.cs b/Editor/UX/UXButtonEditor.cs index 91ade7b..6455cd2 100644 --- a/Editor/UX/UXButtonEditor.cs +++ b/Editor/UX/UXButtonEditor.cs @@ -42,15 +42,11 @@ internal class UXButtonEditor : Editor private SerializedProperty hoverAudioClip; private SerializedProperty clickAudioClip; - -#if INPUTSYSTEM_SUPPORT - private SerializedProperty _hotKeyRefrence; - private SerializedProperty _hotkeyPressType; -#endif - + private UXButton mTarget; private void OnEnable() { + mTarget = (UXButton)target; customSkin = AssetDatabase.LoadAssetAtPath("Packages/com.alicizax.unity.ui.extension/Editor/Res/GUISkin/UIExtensionGUISkin.guiskin"); m_Interactable = serializedObject.FindProperty("m_Interactable"); m_UXGroup = serializedObject.FindProperty("m_UXGroup"); @@ -67,10 +63,6 @@ internal class UXButtonEditor : Editor hoverAudioClip = serializedObject.FindProperty("hoverAudioClip"); clickAudioClip = serializedObject.FindProperty("clickAudioClip"); -#if INPUTSYSTEM_SUPPORT - _hotKeyRefrence = serializedObject.FindProperty("_hotKeyRefrence"); - _hotkeyPressType = serializedObject.FindProperty("_hotkeyPressType"); -#endif CreateChildTransitionList(); } @@ -199,6 +191,11 @@ internal class UXButtonEditor : Editor EditorGUI.EndDisabledGroup(); var interactable = GUILayoutHelper.DrawToggle(m_Interactable.boolValue, customSkin, "Interactable"); + if (interactable != m_Interactable.boolValue) + { + mTarget.Interactable = interactable; + m_SelectionState.enumValueIndex = interactable ? 0 : 4; + } m_Interactable.boolValue = interactable; GUILayout.Space(1); @@ -225,10 +222,6 @@ internal class UXButtonEditor : Editor } EditorGUILayout.Separator(); -#if INPUTSYSTEM_SUPPORT - EditorGUILayout.PropertyField(_hotkeyPressType); - EditorGUILayout.PropertyField(_hotKeyRefrence); -#endif } private void DrawAudioTab() @@ -242,9 +235,9 @@ internal class UXButtonEditor : Editor }); GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () => { - if (hoverAudioClip.objectReferenceValue != null) + if (clickAudioClip.objectReferenceValue != null) { - PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue); + PlayAudio((AudioClip)clickAudioClip.objectReferenceValue); } }); } diff --git a/Editor/UX/UXGroupInspector.cs b/Editor/UX/UXGroupInspector.cs new file mode 100644 index 0000000..53d626d --- /dev/null +++ b/Editor/UX/UXGroupInspector.cs @@ -0,0 +1,50 @@ +using AlicizaX.Editor; +using UnityEditor; + +namespace UnityEngine.UI +{ + [CustomEditor(typeof(UXGroup))] + public class UXGroupInspector : GameFrameworkInspector + { + private SerializedProperty m_Buttons; + private UXGroup _target; + + private void OnEnable() + { + _target = (UXGroup)target; + m_Buttons = serializedObject.FindProperty("m_Buttons"); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + GUI.enabled = false; + DrawHotButtonListAlwaysExpanded(m_Buttons); + GUI.enabled = true; + + EditorGUILayout.Space(); + + serializedObject.ApplyModifiedProperties(); + } + + private void DrawHotButtonListAlwaysExpanded(SerializedProperty listProp) + { + EditorGUILayout.LabelField("Hot Buttons", EditorStyles.boldLabel); + + if (listProp.arraySize == 0) + { + EditorGUILayout.HelpBox("当前没有绑定任何 UXButton", MessageType.Info); + return; + } + + for (int i = 0; i < listProp.arraySize; i++) + { + var element = listProp.GetArrayElementAtIndex(i); + var uxButton = element.objectReferenceValue as UXButton; + string name = uxButton != null ? uxButton.name : "Null"; + EditorGUILayout.ObjectField($"[{i}] {name}", element.objectReferenceValue, typeof(UXButton), true); + } + } + } +} diff --git a/Editor/UX/UXGroupInspector.cs.meta b/Editor/UX/UXGroupInspector.cs.meta new file mode 100644 index 0000000..70a0f1a --- /dev/null +++ b/Editor/UX/UXGroupInspector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 292ca921cd4242d4be9f76bef9bfc08f +timeCreated: 1760343702 \ No newline at end of file diff --git a/Runtime/UXComponent/Button/UXButton.cs b/Runtime/UXComponent/Button/UXButton.cs index 656c825..c3b06c4 100644 --- a/Runtime/UXComponent/Button/UXButton.cs +++ b/Runtime/UXComponent/Button/UXButton.cs @@ -1,14 +1,13 @@ using System; using System.Collections; using System.Collections.Generic; -using AlicizaX; using AlicizaX.UI.Extension; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; -using UnityEngine.Serialization; using UnityEngine.UI; + [Serializable] public enum ButtonModeType { @@ -16,12 +15,12 @@ public enum ButtonModeType Toggle } -[System.Serializable] +[Serializable] public class TransitionData { public Graphic targetGraphic; public Selectable.Transition transition = Selectable.Transition.ColorTint; - public ColorBlock colors; + public ColorBlock colors = ColorBlock.defaultColorBlock; public SpriteState spriteState; public AnimationTriggers animationTriggers = new(); } @@ -51,69 +50,55 @@ public class UXButton : UIBehaviour, IButton, [SerializeField] private UXGroup m_UXGroup; [SerializeField] private AudioClip hoverAudioClip; [SerializeField] private AudioClip clickAudioClip; -#if INPUTSYSTEM_SUPPORT - [SerializeField] internal UnityEngine.InputSystem.InputActionReference _hotKeyRefrence; - [SerializeField] internal EHotkeyPressType _hotkeyPressType; - - internal void OnHotkeyTriggered() - { - if (m_Interactable) - { - OnSubmit(null); - } - } - - internal bool HasHotKeyRefrenced() - { - return _hotKeyRefrence != null; - } - -#endif + [SerializeField] private UnityEvent m_OnValueChanged = new(); #endregion - #region Private Variables + #region Private Fields [SerializeField] private SelectionState m_SelectionState = SelectionState.Normal; - private bool m_DownAndExistUI; private bool m_IsDown; - private bool m_IsTogSelected; + private bool m_HasExitedWhileDown; + private bool _mTogSelected; private Animator _animator; - private WaitForSeconds _waitTimeFadeDuration; private Coroutine _resetRoutine; + private WaitForSeconds _waitFadeDuration; - private readonly Dictionary _animTriggerIDs = new(); - private readonly Dictionary _animResetTriggerIDs = new(); + private static readonly Dictionary _animTriggerCache = new() + { + { "Normal", Animator.StringToHash("Normal") }, + { "Highlighted", Animator.StringToHash("Highlighted") }, + { "Pressed", Animator.StringToHash("Pressed") }, + { "Selected", Animator.StringToHash("Selected") }, + { "Disabled", Animator.StringToHash("Disabled") }, + }; #endregion #region Properties - internal bool Interactable + private Animator Animator => _animator ? _animator : _animator = GetComponent(); + + public bool Interactable { get => m_Interactable; - } - - internal Animator animator - { - get + set { - if (!_animator) - _animator = GetComponent(); - return _animator; + if (m_Interactable == value) return; + m_Interactable = value; + SetState(m_Interactable ? SelectionState.Normal : SelectionState.Disabled); } } - public bool IsSelected + public bool Selected { - get => m_IsTogSelected; + get => _mTogSelected; internal set { - if (m_IsTogSelected == value) return; - m_IsTogSelected = value; - onValueChanged?.Invoke(m_IsTogSelected); - m_SelectionState = value ? SelectionState.Selected : SelectionState.Normal; - UpdateVisualState(m_SelectionState, false); + if (_mTogSelected == value) return; + _mTogSelected = value; + m_OnValueChanged?.Invoke(value); + SetState(value ? SelectionState.Selected : SelectionState.Normal); } } @@ -123,8 +108,6 @@ public class UXButton : UIBehaviour, IButton, set => m_OnClick = value; } - [SerializeField] private UnityEvent m_OnValueChanged = new(); - public UnityEvent onValueChanged { get => m_OnValueChanged; @@ -138,166 +121,96 @@ public class UXButton : UIBehaviour, IButton, protected override void Awake() { base.Awake(); - Initlize(); - } -#if UNITY_EDITOR - protected override void OnValidate() - { - base.OnValidate(); - if (!Application.isPlaying) - { - Initlize(); - } - } - - -#endif // if UNITY_EDITOR - - protected void Initlize() - { - _waitTimeFadeDuration = new WaitForSeconds( - Mathf.Max(0.01f, m_TransitionData.colors.fadeDuration)); - _animTriggerIDs.Clear(); - _animResetTriggerIDs.Clear(); - var triggers = m_TransitionData.animationTriggers; - AddTriggerID(triggers.normalTrigger); - AddTriggerID(triggers.highlightedTrigger); - AddTriggerID(triggers.pressedTrigger); - AddTriggerID(triggers.selectedTrigger); - AddTriggerID(triggers.disabledTrigger); - - - UpdateVisualState(m_SelectionState, true); + _waitFadeDuration = new WaitForSeconds(Mathf.Max(0.01f, m_TransitionData.colors.fadeDuration)); + ApplyVisualState(m_SelectionState, true); } protected override void OnDestroy() { if (_resetRoutine != null) - { StopCoroutine(_resetRoutine); - _resetRoutine = null; - } - base.OnDestroy(); } #endregion - #region Event Handlers + #region Pointer Handlers - void IPointerDownHandler.OnPointerDown(PointerEventData eventData) + public void OnPointerDown(PointerEventData eventData) { - if (!ShouldProcessEvent(eventData)) return; - + if (!CanProcess(eventData)) return; m_IsDown = true; - m_SelectionState = SelectionState.Pressed; - UpdateVisualState(m_SelectionState, false); + SetState(SelectionState.Pressed); } - void IPointerUpHandler.OnPointerUp(PointerEventData eventData) + public void OnPointerUp(PointerEventData eventData) { if (!m_Interactable || eventData.button != PointerEventData.InputButton.Left) return; m_IsDown = false; - if (m_IsTogSelected) - { - m_SelectionState = SelectionState.Selected; - } - else - { - m_SelectionState = m_DownAndExistUI ? SelectionState.Normal : SelectionState.Highlighted; - } + var newState = _mTogSelected + ? SelectionState.Selected + : m_HasExitedWhileDown + ? SelectionState.Normal + : SelectionState.Highlighted; - UpdateVisualState(m_SelectionState, false); + SetState(newState); } - void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData) + public void OnPointerEnter(PointerEventData eventData) { - if (!ShouldProcessEvent(eventData)) return; - - m_DownAndExistUI = false; + if (!CanProcess(eventData)) return; + m_HasExitedWhileDown = false; if (m_IsDown) return; - m_SelectionState = SelectionState.Highlighted; - UpdateVisualState(m_SelectionState, false); - PlayButtonSound(hoverAudioClip); + SetState(SelectionState.Highlighted); + PlayAudio(hoverAudioClip); } - void IPointerExitHandler.OnPointerExit(PointerEventData eventData) + public void OnPointerExit(PointerEventData eventData) { if (!m_Interactable) return; if (m_IsDown) { - m_DownAndExistUI = true; + m_HasExitedWhileDown = true; return; } - m_SelectionState = IsSelected ? SelectionState.Selected : SelectionState.Normal; - UpdateVisualState(m_SelectionState, false); + SetState(_mTogSelected ? SelectionState.Selected : SelectionState.Normal); } - void IPointerClickHandler.OnPointerClick(PointerEventData eventData) + public void OnPointerClick(PointerEventData eventData) { if (eventData.button != PointerEventData.InputButton.Left || !m_Interactable) return; - PlayButtonSound(clickAudioClip); - ProcessClick(); + PlayAudio(clickAudioClip); + HandleClick(); } - public void OnSubmit(BaseEventData eventData) { - UpdateVisualState(SelectionState.Pressed, false); - ProcessClick(); + SetState(SelectionState.Pressed); + HandleClick(); if (_resetRoutine != null) - StopCoroutine(OnFinishSubmit()); - - _resetRoutine = StartCoroutine(OnFinishSubmit()); - } - - public bool Disable - { - get => m_Interactable; - set - { - if (m_Interactable == value) return; - m_Interactable = value; - m_SelectionState = !m_Interactable ? SelectionState.Disabled : SelectionState.Normal; - UpdateVisualState(m_SelectionState, false); - } + StopCoroutine(_resetRoutine); + _resetRoutine = StartCoroutine(ResetAfterSubmit()); } #endregion - #region Public Methods + #region Logic - public bool Selected - { - get => IsSelected; - set - { - if (m_Mode == ButtonModeType.Toggle) - { - ProcessClick(); - } - } - } - - #endregion - - #region Private Methods - - private bool ShouldProcessEvent(PointerEventData eventData) + private bool CanProcess(PointerEventData eventData) { return m_Interactable && eventData.button == PointerEventData.InputButton.Left && - !(m_Mode == ButtonModeType.Toggle && IsSelected); + !(m_Mode == ButtonModeType.Toggle && Selected); } - private void ProcessClick() + private void HandleClick() { if (m_Mode == ButtonModeType.Normal) { @@ -310,142 +223,87 @@ public class UXButton : UIBehaviour, IButton, } else { - IsSelected = !IsSelected; + Selected = !Selected; } } - private void UpdateVisualState(SelectionState state, bool instant) + private void SetState(SelectionState state) { - ProcessTransitionData(m_TransitionData, state, instant); - foreach (var transition in m_ChildTransitions) - { - ProcessTransitionData(transition, state, instant); - } + if (m_SelectionState == state) return; + m_SelectionState = state; + ApplyVisualState(state, false); } - private void ProcessTransitionData(TransitionData transition, SelectionState state, bool instant) + private void ApplyVisualState(SelectionState state, bool instant) { - if ((transition.transition == Selectable.Transition.ColorTint || transition.transition == Selectable.Transition.SpriteSwap) && transition.targetGraphic == null) return; - - Color tintColor; - Sprite transitionSprite; - string triggerName; - - switch (state) - { - case SelectionState.Normal: - tintColor = transition.colors.normalColor; - transitionSprite = transition.spriteState.highlightedSprite; - triggerName = transition.animationTriggers.normalTrigger; - break; - case SelectionState.Highlighted: - tintColor = transition.colors.highlightedColor; - transitionSprite = transition.spriteState.highlightedSprite; - triggerName = transition.animationTriggers.highlightedTrigger; - break; - case SelectionState.Pressed: - tintColor = transition.colors.pressedColor; - transitionSprite = transition.spriteState.pressedSprite; - triggerName = transition.animationTriggers.pressedTrigger; - break; - case SelectionState.Selected: - tintColor = transition.colors.selectedColor; - transitionSprite = transition.spriteState.selectedSprite; - triggerName = transition.animationTriggers.selectedTrigger; - break; - case SelectionState.Disabled: - tintColor = transition.colors.disabledColor; - transitionSprite = transition.spriteState.disabledSprite; - triggerName = transition.animationTriggers.disabledTrigger; - break; - default: - return; - } - - switch (transition.transition) - { - case Selectable.Transition.ColorTint: - StartColorTween(transition, tintColor * transition.colors.colorMultiplier, instant); - break; - case Selectable.Transition.SpriteSwap: - DoSpriteSwap(transition, transitionSprite); - break; - case Selectable.Transition.Animation: - TriggerAnimation(triggerName); - break; - } + ApplyTransition(m_TransitionData, state, instant); + for (int i = 0; i < m_ChildTransitions.Count; i++) + ApplyTransition(m_ChildTransitions[i], state, instant); } - private void StartColorTween(TransitionData data, Color targetColor, bool instant) + private void ApplyTransition(TransitionData data, SelectionState state, bool instant) { - if (Application.isPlaying) - { - data.targetGraphic.CrossFadeColor( - targetColor, - instant ? 0f : data.colors.fadeDuration, - true, - true - ); - } - else - { - data.targetGraphic.canvasRenderer.SetColor(targetColor); - } - } - - private void DoSpriteSwap(TransitionData data, Sprite newSprite) - { - if (data.targetGraphic is Image image) - { - image.overrideSprite = newSprite; - } - } - - private void TriggerAnimation(string trigger) - { - if (animator == null || - !animator.isActiveAndEnabled || - !animator.hasBoundPlayables || - string.IsNullOrEmpty(trigger)) + if (data.targetGraphic == null && data.transition != Selectable.Transition.Animation) return; - foreach (var resetTrigger in _animResetTriggerIDs.Keys) + (Color color, Sprite sprite, string trigger) = state switch { - animator.ResetTrigger(_animTriggerIDs[resetTrigger]); - } + SelectionState.Normal => (data.colors.normalColor, data.spriteState.highlightedSprite, data.animationTriggers.normalTrigger), + SelectionState.Highlighted => (data.colors.highlightedColor, data.spriteState.highlightedSprite, data.animationTriggers.highlightedTrigger), + SelectionState.Pressed => (data.colors.pressedColor, data.spriteState.pressedSprite, data.animationTriggers.pressedTrigger), + SelectionState.Selected => (data.colors.selectedColor, data.spriteState.selectedSprite, data.animationTriggers.selectedTrigger), + SelectionState.Disabled => (data.colors.disabledColor, data.spriteState.disabledSprite, data.animationTriggers.disabledTrigger), + _ => (Color.white, null, null) + }; - if (_animTriggerIDs.TryGetValue(trigger, out int id)) + switch (data.transition) { - animator.SetTrigger(id); + case Selectable.Transition.ColorTint: + TweenColor(data, color * data.colors.colorMultiplier, instant); + break; + case Selectable.Transition.SpriteSwap: + SwapSprite(data, sprite); + break; + case Selectable.Transition.Animation: + PlayAnimation(trigger); + break; } } - private void AddTriggerID(string triggerName) + private void TweenColor(TransitionData data, Color color, bool instant) { - if (!string.IsNullOrEmpty(triggerName)) - { - int id = Animator.StringToHash(triggerName); - if (!_animTriggerIDs.ContainsKey(triggerName)) - { - _animTriggerIDs.Add(triggerName, id); - _animResetTriggerIDs.Add(triggerName, id); - } - } + data.targetGraphic.CrossFadeColor(color, instant ? 0f : data.colors.fadeDuration, true, true); } - private void PlayButtonSound(AudioClip clip) + private static void SwapSprite(TransitionData data, Sprite sprite) { - if (clip == null || UXComponentExtensionsHelper.AudioHelper == null) return; - UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip); + if (data.targetGraphic is Image img) + img.overrideSprite = sprite; } - private IEnumerator OnFinishSubmit() + private void PlayAnimation(string trigger) { - yield return _waitTimeFadeDuration; - UpdateVisualState( - m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal, - false - ); + if (!Animator || !Animator.isActiveAndEnabled || string.IsNullOrEmpty(trigger)) + return; + + foreach (int id in _animTriggerCache.Values) + Animator.ResetTrigger(id); + + if (_animTriggerCache.TryGetValue(trigger, out int hash)) + Animator.SetTrigger(hash); + } + + + private void PlayAudio(AudioClip clip) + { + if (clip && UXComponentExtensionsHelper.AudioHelper != null) + UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip); + } + + private IEnumerator ResetAfterSubmit() + { + yield return _waitFadeDuration; + SetState(_mTogSelected ? SelectionState.Selected : SelectionState.Normal); _resetRoutine = null; } diff --git a/Runtime/UXComponent/Group/UXGroup.cs b/Runtime/UXComponent/Group/UXGroup.cs index 667c71d..deae8ea 100644 --- a/Runtime/UXComponent/Group/UXGroup.cs +++ b/Runtime/UXComponent/Group/UXGroup.cs @@ -1,17 +1,17 @@ +using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; -using System.Collections.Generic; -using Sirenix.OdinInspector; using UnityEngine.EventSystems; + [DisallowMultipleComponent] public class UXGroup : UIBehaviour { [SerializeField] private bool m_AllowSwitchOff; - [ReadOnly, SerializeField] private List m_Buttons = new(); + [SerializeField] private List m_Buttons = new(); private UXButton _current; - private readonly HashSet _registeredButtons = new(); + private readonly HashSet _registered = new(); public UnityEvent onSelectedChanged = new(); @@ -21,169 +21,118 @@ public class UXGroup : UIBehaviour set { m_AllowSwitchOff = value; - ValidateGroupState(); + ValidateGroup(); } } + protected override void Awake() => ValidateGroup(); + protected override void OnDestroy() { - foreach (var button in _registeredButtons) - { - if (button) button.IsSelected = false; - } - + foreach (var btn in _registered) + if (btn) + btn.Selected = false; + _registered.Clear(); m_Buttons.Clear(); - _registeredButtons.Clear(); base.OnDestroy(); } - protected override void Awake() => ValidateGroupState(); - public void RegisterButton(UXButton button) { - if (!button || _registeredButtons.Contains(button)) return; + if (!button || !_registered.Add(button)) return; + if (!m_Buttons.Contains(button)) m_Buttons.Add(button); - m_Buttons.Add(button); - _registeredButtons.Add(button); - - if (button.IsSelected) + if (button.Selected) { if (_current && _current != button) - _current.IsSelected = false; + _current.Selected = false; _current = button; } - ValidateGroupState(); + ValidateGroup(); } public void UnregisterButton(UXButton button) { - if (!button || !_registeredButtons.Contains(button)) return; - + if (!button || !_registered.Remove(button)) return; m_Buttons.Remove(button); - _registeredButtons.Remove(button); - button.IsSelected = false; - if (_current == button) _current = null; + button.Selected = false; } - internal void NotifyButtonClicked(UXButton clickedButton) + internal void NotifyButtonClicked(UXButton button) { - if (clickedButton.IsSelected) + if (!button) return; + + if (button.Selected) { - if (m_AllowSwitchOff) - SetSelectedButton(null); + if (m_AllowSwitchOff) SetSelected(null); } else { - SetSelectedButton(clickedButton); + SetSelected(button); } } - private void SetSelectedButton(UXButton target) + private void SetSelected(UXButton target) { var previous = _current; _current = null; - foreach (var button in m_Buttons) + foreach (var btn in m_Buttons) { - bool shouldSelect = (button == target); - if (button.IsSelected != shouldSelect) - button.IsSelected = shouldSelect; - - if (shouldSelect) - _current = button; + bool select = (btn == target); + if (btn.Selected != select) + btn.Selected = select; + if (select) _current = btn; } if (previous != _current) onSelectedChanged?.Invoke(_current); } - private void ValidateGroupState() + private void ValidateGroup() { - bool hasSelected = _current != null && _current.IsSelected; - if (!hasSelected && m_Buttons.Count > 0 && !m_AllowSwitchOff) - SetSelectedButton(m_Buttons[0]); + if (_current != null && _current.Selected) return; + + if (!m_AllowSwitchOff && m_Buttons.Count > 0) + SetSelected(m_Buttons[0]); } - public bool AnyOtherSelected(UXButton exclusion) + public bool AnyOtherSelected(UXButton exclude) { - foreach (var button in m_Buttons) - { - if (button != exclusion && button.IsSelected) + foreach (var btn in m_Buttons) + if (btn != exclude && btn.Selected) return true; - } - return false; } + public void SelectNext() => SelectRelative(1); + public void SelectPrevious() => SelectRelative(-1); - public void SelectPrevious() + private void SelectRelative(int dir) { if (m_Buttons.Count == 0) return; - - int startIndex = _current ? m_Buttons.IndexOf(_current) : -1; - int newIndex = FindSelectableIndex(startIndex, -1); - - if (newIndex != -1) - { - SetSelectedButton(m_Buttons[newIndex]); - } + int start = _current ? m_Buttons.IndexOf(_current) : -1; + int next = FindNextSelectable(start, dir); + if (next >= 0) SetSelected(m_Buttons[next]); } - - public void SelectNext() - { - if (m_Buttons.Count == 0) return; - - int startIndex = _current ? m_Buttons.IndexOf(_current) : -1; - int newIndex = FindSelectableIndex(startIndex, 1); - - if (newIndex != -1) - { - SetSelectedButton(m_Buttons[newIndex]); - } - } - - - private int FindSelectableIndex(int startIndex, int direction) + private int FindNextSelectable(int startIndex, int dir) { if (m_Buttons.Count == 0) return -1; - int buttonCount = m_Buttons.Count - 1; - int valueIndex = startIndex == -1 ? 0 : startIndex + direction; - int fallBackIndex = -1; + int count = m_Buttons.Count; + int index = (startIndex + dir + count) % count; - valueIndex = valueIndex > buttonCount ? 0 : (valueIndex < 0 ? buttonCount : valueIndex); - if (valueIndex > buttonCount) + for (int i = 0; i < count; i++) { - valueIndex = 0; - } - else if (valueIndex < 0) - { - valueIndex = buttonCount; + var btn = m_Buttons[index]; + if (btn && btn.isActiveAndEnabled && btn.Interactable) + return index; + index = (index + dir + count) % count; } - while (valueIndex != startIndex) - { - UXButton btn = m_Buttons[valueIndex]; - if (btn.isActiveAndEnabled && btn.Interactable) - { - fallBackIndex = valueIndex; - break; - } - - valueIndex += direction; - if (valueIndex > buttonCount) - { - valueIndex = 0; - } - else if (valueIndex < 0) - { - valueIndex = buttonCount; - } - } - - return fallBackIndex; + return -1; } } diff --git a/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs b/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs index c6546ef..0dd4227 100644 --- a/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs +++ b/Runtime/UXComponent/Hotkey/HotkeyBindComponent.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; +#if INPUTSYSTEM_SUPPORT using AlicizaX.UI.Runtime; -using Sirenix.OdinInspector; using UnityEngine; namespace AlicizaX.UI.Extension.UXComponent.Hotkey @@ -22,30 +20,8 @@ namespace AlicizaX.UI.Extension.UXComponent.Hotkey _holderObjectBase.OnWindowShowEvent -= BindHotKeys; _holderObjectBase.OnWindowClosedEvent -= UnBindHotKeys; } -#if UNITY_EDITOR - [InlineButton("SetHotKeyButtons")] -#endif [SerializeField] - [HideLabel] - private UXButton[] hotButtons; - - -#if UNITY_EDITOR - private void SetHotKeyButtons() - { - var btns = transform.GetComponentsInChildren(true); - var hotBtnList = new List(); - for (int i = 0; i < btns.Length; i++) - { - if (btns[i].HasHotKeyRefrenced()) - { - hotBtnList.Add(btns[i]); - } - } - - hotButtons = hotBtnList.ToArray(); - } -#endif + private UXHotkey[] hotButtons; internal void BindHotKeys() { @@ -64,3 +40,5 @@ namespace AlicizaX.UI.Extension.UXComponent.Hotkey } } } + +#endif diff --git a/Runtime/UXComponent/Hotkey/UXHotkey.cs b/Runtime/UXComponent/Hotkey/UXHotkey.cs index 28911e8..53d3834 100644 --- a/Runtime/UXComponent/Hotkey/UXHotkey.cs +++ b/Runtime/UXComponent/Hotkey/UXHotkey.cs @@ -1,174 +1,28 @@ #if INPUTSYSTEM_SUPPORT +using System; using UnityEngine; -using UnityEngine.InputSystem; -using System.Collections.Generic; -using System.Collections; -internal enum EHotkeyPressType +namespace AlicizaX.UI.Extension { - Started, - Performed -} - -internal static class UXHotkeyComponent -{ - private readonly struct HotkeyRegistration + public class UXHotkey : MonoBehaviour { - public readonly EHotkeyPressType pressType; - public readonly UXButton button; + [SerializeField] private UXButton _button; - public HotkeyRegistration(UXButton btn, EHotkeyPressType pressType) + [SerializeField] internal UnityEngine.InputSystem.InputActionReference _hotKeyRefrence; + [SerializeField] internal EHotkeyPressType _hotkeyPressType; + + private void OnValidate() { - button = btn; - this.pressType = pressType; - } - } - - private static readonly Dictionary> _hotkeyRegistry = - new Dictionary>(32); - - - private static readonly Dictionary handler, InputActionReference action)> _sharedHandlers = - new Dictionary, InputActionReference)>(32); - - - private static readonly Dictionary _buttonRegistrations = - new Dictionary(64); - - -#if UNITY_EDITOR - [UnityEditor.Callbacks.DidReloadScripts] - internal static void ClearHotkeyRegistry() - { - foreach (var key in _buttonRegistrations.Keys) - { - UnregisterHotkey(key); + _button = GetComponent(); } - _sharedHandlers.Clear(); - _hotkeyRegistry.Clear(); - _buttonRegistrations.Clear(); - } -#endif - - internal static void RegisterHotkey(UXButton button, InputActionReference action, EHotkeyPressType pressType) - { - if (action == null || action.action == null || button == null) - return; - - string actionId = action.action.id.ToString(); - - - HotkeyRegistration registration = new HotkeyRegistration(button, pressType); - - - if (!_hotkeyRegistry.TryGetValue(actionId, out var registrations)) + internal void HotkeyActionTrigger() { - registrations = new List(4); - _hotkeyRegistry[actionId] = registrations; - } - - registrations.Add(registration); - - - _buttonRegistrations[button] = actionId; - - if (!_sharedHandlers.ContainsKey(actionId)) - { - System.Action handler = ctx => OnHotkeyTriggered(actionId); - _sharedHandlers[actionId] = (handler, action); - - switch (pressType) + if (_button.Interactable) { - case EHotkeyPressType.Started: - action.action.started += handler; - break; - case EHotkeyPressType.Performed: - action.action.performed += handler; - break; - } - - action.action.Enable(); - } - } - - public static void UnregisterHotkey(UXButton button) - { - if (button == null || !_buttonRegistrations.TryGetValue(button, out var actionId)) - return; - - - if (_hotkeyRegistry.TryGetValue(actionId, out var registrations)) - { - HotkeyRegistration hotkeyInfo; - for (int i = registrations.Count - 1; i >= 0; i--) - { - if (registrations[i].button == button) - { - hotkeyInfo = registrations[i]; - registrations.RemoveAt(i); - - if (registrations.Count == 0 && _sharedHandlers.TryGetValue(actionId, out var handlerInfo)) - { - var (handler, actionRef) = handlerInfo; - if (actionRef != null && actionRef.action != null) - { - actionRef.action.Disable(); - _sharedHandlers.Remove(actionId); - _hotkeyRegistry.Remove(actionId); - - switch (hotkeyInfo.pressType) - { - case EHotkeyPressType.Started: - actionRef.action.started -= handler; - break; - case EHotkeyPressType.Performed: - actionRef.action.performed -= handler; - break; - } - } - } - - break; - } + _button.OnSubmit(null); } } - - - _buttonRegistrations.Remove(button); - } - - private static void OnHotkeyTriggered(string actionId) - { - if (_hotkeyRegistry.TryGetValue(actionId, out var registrations) && registrations.Count > 0) - { - var registration = registrations[registrations.Count - 1]; - registration.button.OnHotkeyTriggered(); - } - } -} - -public static class UXButtonHotkeyExtension -{ - public static void BindHotKey(this UXButton button) - { - if (button == null) return; - - - if (button._hotKeyRefrence != null) - { - UXHotkeyComponent.RegisterHotkey( - button, - button._hotKeyRefrence, - button._hotkeyPressType - ); - } - } - - public static void UnBindHotKey(this UXButton button) - { - if (button == null || button._hotKeyRefrence == null) return; - UXHotkeyComponent.UnregisterHotkey(button); } } #endif diff --git a/Runtime/UXComponent/Hotkey/UXHotkey.cs.meta b/Runtime/UXComponent/Hotkey/UXHotkey.cs.meta index 9064b0e..aa1f0fb 100644 --- a/Runtime/UXComponent/Hotkey/UXHotkey.cs.meta +++ b/Runtime/UXComponent/Hotkey/UXHotkey.cs.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: 0aa77908962c48199d63710fa15b8c37 -timeCreated: 1754555268 \ No newline at end of file +guid: 9aa1d0e145a6435c94190809bbc99235 +timeCreated: 1760340422 \ No newline at end of file diff --git a/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs b/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs new file mode 100644 index 0000000..52d48b9 --- /dev/null +++ b/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs @@ -0,0 +1,174 @@ +#if INPUTSYSTEM_SUPPORT +using UnityEngine; +using UnityEngine.InputSystem; +using System.Collections.Generic; +using System.Collections; +using AlicizaX.UI.Extension; +internal enum EHotkeyPressType +{ + Started, + Performed +} + +internal static class UXHotkeyRegisterManager +{ + private readonly struct HotkeyRegistration + { + public readonly EHotkeyPressType pressType; + public readonly UXHotkey button; + + public HotkeyRegistration(UXHotkey btn, EHotkeyPressType pressType) + { + button = btn; + this.pressType = pressType; + } + } + + private static readonly Dictionary> _hotkeyRegistry = + new Dictionary>(32); + + + private static readonly Dictionary handler, InputActionReference action)> _sharedHandlers = + new Dictionary, InputActionReference)>(32); + + + private static readonly Dictionary _buttonRegistrations = + new Dictionary(64); + + +#if UNITY_EDITOR + [UnityEditor.Callbacks.DidReloadScripts] + internal static void ClearHotkeyRegistry() + { + foreach (var key in _buttonRegistrations.Keys) + { + UnregisterHotkey(key); + } + + _sharedHandlers.Clear(); + _hotkeyRegistry.Clear(); + _buttonRegistrations.Clear(); + } +#endif + + internal static void RegisterHotkey(UXHotkey button, InputActionReference action, EHotkeyPressType pressType) + { + if (action == null || action.action == null || button == null) + return; + + string actionId = action.action.id.ToString(); + + + HotkeyRegistration registration = new HotkeyRegistration(button, pressType); + + + if (!_hotkeyRegistry.TryGetValue(actionId, out var registrations)) + { + registrations = new List(4); + _hotkeyRegistry[actionId] = registrations; + } + + registrations.Add(registration); + + + _buttonRegistrations[button] = actionId; + + if (!_sharedHandlers.ContainsKey(actionId)) + { + System.Action handler = ctx => OnHotkeyTriggered(actionId); + _sharedHandlers[actionId] = (handler, action); + + switch (pressType) + { + case EHotkeyPressType.Started: + action.action.started += handler; + break; + case EHotkeyPressType.Performed: + action.action.performed += handler; + break; + } + + action.action.Enable(); + } + } + + public static void UnregisterHotkey(UXHotkey button) + { + if (button == null || !_buttonRegistrations.TryGetValue(button, out var actionId)) + return; + + + if (_hotkeyRegistry.TryGetValue(actionId, out var registrations)) + { + HotkeyRegistration hotkeyInfo; + for (int i = registrations.Count - 1; i >= 0; i--) + { + if (registrations[i].button == button) + { + hotkeyInfo = registrations[i]; + registrations.RemoveAt(i); + + if (registrations.Count == 0 && _sharedHandlers.TryGetValue(actionId, out var handlerInfo)) + { + var (handler, actionRef) = handlerInfo; + if (actionRef != null && actionRef.action != null) + { + actionRef.action.Disable(); + _sharedHandlers.Remove(actionId); + _hotkeyRegistry.Remove(actionId); + + switch (hotkeyInfo.pressType) + { + case EHotkeyPressType.Started: + actionRef.action.started -= handler; + break; + case EHotkeyPressType.Performed: + actionRef.action.performed -= handler; + break; + } + } + } + + break; + } + } + } + + + _buttonRegistrations.Remove(button); + } + + private static void OnHotkeyTriggered(string actionId) + { + if (_hotkeyRegistry.TryGetValue(actionId, out var registrations) && registrations.Count > 0) + { + var registration = registrations[^1]; + registration.button.HotkeyActionTrigger(); + } + } +} + +public static class UXHotkeyHotkeyExtension +{ + public static void BindHotKey(this UXHotkey button) + { + if (button == null) return; + + + if (button._hotKeyRefrence != null) + { + UXHotkeyRegisterManager.RegisterHotkey( + button, + button._hotKeyRefrence, + button._hotkeyPressType + ); + } + } + + public static void UnBindHotKey(this UXHotkey button) + { + if (button == null || button._hotKeyRefrence == null) return; + UXHotkeyRegisterManager.UnregisterHotkey(button); + } +} +#endif diff --git a/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs.meta b/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs.meta new file mode 100644 index 0000000..9064b0e --- /dev/null +++ b/Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0aa77908962c48199d63710fa15b8c37 +timeCreated: 1754555268 \ No newline at end of file