This commit is contained in:
陈思海 2025-10-13 20:20:01 +08:00
parent 8bbfa59524
commit d8fd91d32c
15 changed files with 592 additions and 565 deletions

3
Editor/UX/Hotkey.meta Normal file
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: be711d9c48d440c4afccffac091ccc7d
timeCreated: 1760340542

View File

@ -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<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.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--;
}
/// <summary>
/// 查找所有子物体(包含隐藏)并绑定 UXHotkey
/// </summary>
private void FindAllUXHotkeys()
{
Undo.RecordObject(_target, "Scan UXHotkey");
var uxHotkeys = _target.GetComponentsInChildren<UXHotkey>(true);
List<UXHotkey> valiedHotkeys = new List<UXHotkey>();
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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f9779e310e684e9497bdac4f4ac3983e
timeCreated: 1760340548

View File

@ -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<UXButton>();
if (holder == null)
{
EditorGUILayout.HelpBox(
"⚠ 当前对象缺少 UXButton 组件",
MessageType.Error
);
}
EditorGUILayout.PropertyField(_button);
EditorGUILayout.PropertyField(_hotKeyRefrence);
EditorGUILayout.PropertyField(_hotkeyPressType);
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 87a10dc3a1544ec586ff27809213a127
timeCreated: 1760341800

View File

@ -42,15 +42,11 @@ internal class UXButtonEditor : Editor
private SerializedProperty hoverAudioClip; private SerializedProperty hoverAudioClip;
private SerializedProperty clickAudioClip; private SerializedProperty clickAudioClip;
private UXButton mTarget;
#if INPUTSYSTEM_SUPPORT
private SerializedProperty _hotKeyRefrence;
private SerializedProperty _hotkeyPressType;
#endif
private void OnEnable() private void OnEnable()
{ {
mTarget = (UXButton)target;
customSkin = AssetDatabase.LoadAssetAtPath<GUISkin>("Packages/com.alicizax.unity.ui.extension/Editor/Res/GUISkin/UIExtensionGUISkin.guiskin"); customSkin = AssetDatabase.LoadAssetAtPath<GUISkin>("Packages/com.alicizax.unity.ui.extension/Editor/Res/GUISkin/UIExtensionGUISkin.guiskin");
m_Interactable = serializedObject.FindProperty("m_Interactable"); m_Interactable = serializedObject.FindProperty("m_Interactable");
m_UXGroup = serializedObject.FindProperty("m_UXGroup"); m_UXGroup = serializedObject.FindProperty("m_UXGroup");
@ -67,10 +63,6 @@ internal class UXButtonEditor : Editor
hoverAudioClip = serializedObject.FindProperty("hoverAudioClip"); hoverAudioClip = serializedObject.FindProperty("hoverAudioClip");
clickAudioClip = serializedObject.FindProperty("clickAudioClip"); clickAudioClip = serializedObject.FindProperty("clickAudioClip");
#if INPUTSYSTEM_SUPPORT
_hotKeyRefrence = serializedObject.FindProperty("_hotKeyRefrence");
_hotkeyPressType = serializedObject.FindProperty("_hotkeyPressType");
#endif
CreateChildTransitionList(); CreateChildTransitionList();
} }
@ -199,6 +191,11 @@ internal class UXButtonEditor : Editor
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
var interactable = GUILayoutHelper.DrawToggle(m_Interactable.boolValue, customSkin, "Interactable"); 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; m_Interactable.boolValue = interactable;
GUILayout.Space(1); GUILayout.Space(1);
@ -225,10 +222,6 @@ internal class UXButtonEditor : Editor
} }
EditorGUILayout.Separator(); EditorGUILayout.Separator();
#if INPUTSYSTEM_SUPPORT
EditorGUILayout.PropertyField(_hotkeyPressType);
EditorGUILayout.PropertyField(_hotKeyRefrence);
#endif
} }
private void DrawAudioTab() private void DrawAudioTab()
@ -242,9 +235,9 @@ internal class UXButtonEditor : Editor
}); });
GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () => GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () =>
{ {
if (hoverAudioClip.objectReferenceValue != null) if (clickAudioClip.objectReferenceValue != null)
{ {
PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue); PlayAudio((AudioClip)clickAudioClip.objectReferenceValue);
} }
}); });
} }

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 292ca921cd4242d4be9f76bef9bfc08f
timeCreated: 1760343702

View File

@ -1,14 +1,13 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using AlicizaX;
using AlicizaX.UI.Extension; using AlicizaX.UI.Extension;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI; using UnityEngine.UI;
[Serializable] [Serializable]
public enum ButtonModeType public enum ButtonModeType
{ {
@ -16,12 +15,12 @@ public enum ButtonModeType
Toggle Toggle
} }
[System.Serializable] [Serializable]
public class TransitionData public class TransitionData
{ {
public Graphic targetGraphic; public Graphic targetGraphic;
public Selectable.Transition transition = Selectable.Transition.ColorTint; public Selectable.Transition transition = Selectable.Transition.ColorTint;
public ColorBlock colors; public ColorBlock colors = ColorBlock.defaultColorBlock;
public SpriteState spriteState; public SpriteState spriteState;
public AnimationTriggers animationTriggers = new(); public AnimationTriggers animationTriggers = new();
} }
@ -51,69 +50,55 @@ public class UXButton : UIBehaviour, IButton,
[SerializeField] private UXGroup m_UXGroup; [SerializeField] private UXGroup m_UXGroup;
[SerializeField] private AudioClip hoverAudioClip; [SerializeField] private AudioClip hoverAudioClip;
[SerializeField] private AudioClip clickAudioClip; [SerializeField] private AudioClip clickAudioClip;
#if INPUTSYSTEM_SUPPORT [SerializeField] private UnityEvent<bool> m_OnValueChanged = new();
[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
#endregion #endregion
#region Private Variables #region Private Fields
[SerializeField] private SelectionState m_SelectionState = SelectionState.Normal; [SerializeField] private SelectionState m_SelectionState = SelectionState.Normal;
private bool m_DownAndExistUI;
private bool m_IsDown; private bool m_IsDown;
private bool m_IsTogSelected; private bool m_HasExitedWhileDown;
private bool _mTogSelected;
private Animator _animator; private Animator _animator;
private WaitForSeconds _waitTimeFadeDuration;
private Coroutine _resetRoutine; private Coroutine _resetRoutine;
private WaitForSeconds _waitFadeDuration;
private readonly Dictionary<string, int> _animTriggerIDs = new(); private static readonly Dictionary<string, int> _animTriggerCache = new()
private readonly Dictionary<string, int> _animResetTriggerIDs = new(); {
{ "Normal", Animator.StringToHash("Normal") },
{ "Highlighted", Animator.StringToHash("Highlighted") },
{ "Pressed", Animator.StringToHash("Pressed") },
{ "Selected", Animator.StringToHash("Selected") },
{ "Disabled", Animator.StringToHash("Disabled") },
};
#endregion #endregion
#region Properties #region Properties
internal bool Interactable private Animator Animator => _animator ? _animator : _animator = GetComponent<Animator>();
public bool Interactable
{ {
get => m_Interactable; get => m_Interactable;
} set
internal Animator animator
{ {
get if (m_Interactable == value) return;
{ m_Interactable = value;
if (!_animator) SetState(m_Interactable ? SelectionState.Normal : SelectionState.Disabled);
_animator = GetComponent<Animator>();
return _animator;
} }
} }
public bool IsSelected public bool Selected
{ {
get => m_IsTogSelected; get => _mTogSelected;
internal set internal set
{ {
if (m_IsTogSelected == value) return; if (_mTogSelected == value) return;
m_IsTogSelected = value; _mTogSelected = value;
onValueChanged?.Invoke(m_IsTogSelected); m_OnValueChanged?.Invoke(value);
m_SelectionState = value ? SelectionState.Selected : SelectionState.Normal; SetState(value ? SelectionState.Selected : SelectionState.Normal);
UpdateVisualState(m_SelectionState, false);
} }
} }
@ -123,8 +108,6 @@ public class UXButton : UIBehaviour, IButton,
set => m_OnClick = value; set => m_OnClick = value;
} }
[SerializeField] private UnityEvent<bool> m_OnValueChanged = new();
public UnityEvent<bool> onValueChanged public UnityEvent<bool> onValueChanged
{ {
get => m_OnValueChanged; get => m_OnValueChanged;
@ -138,166 +121,96 @@ public class UXButton : UIBehaviour, IButton,
protected override void Awake() protected override void Awake()
{ {
base.Awake(); base.Awake();
Initlize(); _waitFadeDuration = new WaitForSeconds(Mathf.Max(0.01f, m_TransitionData.colors.fadeDuration));
} ApplyVisualState(m_SelectionState, true);
#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);
} }
protected override void OnDestroy() protected override void OnDestroy()
{ {
if (_resetRoutine != null) if (_resetRoutine != null)
{
StopCoroutine(_resetRoutine); StopCoroutine(_resetRoutine);
_resetRoutine = null;
}
base.OnDestroy(); base.OnDestroy();
} }
#endregion #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_IsDown = true;
m_SelectionState = SelectionState.Pressed; SetState(SelectionState.Pressed);
UpdateVisualState(m_SelectionState, false);
} }
void IPointerUpHandler.OnPointerUp(PointerEventData eventData) public void OnPointerUp(PointerEventData eventData)
{ {
if (!m_Interactable || eventData.button != PointerEventData.InputButton.Left) if (!m_Interactable || eventData.button != PointerEventData.InputButton.Left)
return; return;
m_IsDown = false; m_IsDown = false;
if (m_IsTogSelected) var newState = _mTogSelected
{ ? SelectionState.Selected
m_SelectionState = SelectionState.Selected; : m_HasExitedWhileDown
} ? SelectionState.Normal
else : SelectionState.Highlighted;
{
m_SelectionState = m_DownAndExistUI ? SelectionState.Normal : SelectionState.Highlighted; SetState(newState);
} }
UpdateVisualState(m_SelectionState, false); public void OnPointerEnter(PointerEventData eventData)
}
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
{ {
if (!ShouldProcessEvent(eventData)) return; if (!CanProcess(eventData)) return;
m_HasExitedWhileDown = false;
m_DownAndExistUI = false;
if (m_IsDown) return; if (m_IsDown) return;
m_SelectionState = SelectionState.Highlighted; SetState(SelectionState.Highlighted);
UpdateVisualState(m_SelectionState, false); PlayAudio(hoverAudioClip);
PlayButtonSound(hoverAudioClip);
} }
void IPointerExitHandler.OnPointerExit(PointerEventData eventData) public void OnPointerExit(PointerEventData eventData)
{ {
if (!m_Interactable) return; if (!m_Interactable) return;
if (m_IsDown) if (m_IsDown)
{ {
m_DownAndExistUI = true; m_HasExitedWhileDown = true;
return; return;
} }
m_SelectionState = IsSelected ? SelectionState.Selected : SelectionState.Normal; SetState(_mTogSelected ? SelectionState.Selected : SelectionState.Normal);
UpdateVisualState(m_SelectionState, false);
} }
void IPointerClickHandler.OnPointerClick(PointerEventData eventData) public void OnPointerClick(PointerEventData eventData)
{ {
if (eventData.button != PointerEventData.InputButton.Left || !m_Interactable) if (eventData.button != PointerEventData.InputButton.Left || !m_Interactable)
return; return;
PlayButtonSound(clickAudioClip); PlayAudio(clickAudioClip);
ProcessClick(); HandleClick();
} }
public void OnSubmit(BaseEventData eventData) public void OnSubmit(BaseEventData eventData)
{ {
UpdateVisualState(SelectionState.Pressed, false); SetState(SelectionState.Pressed);
ProcessClick(); HandleClick();
if (_resetRoutine != null) if (_resetRoutine != null)
StopCoroutine(OnFinishSubmit()); StopCoroutine(_resetRoutine);
_resetRoutine = StartCoroutine(ResetAfterSubmit());
_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);
}
} }
#endregion #endregion
#region Public Methods #region Logic
public bool Selected private bool CanProcess(PointerEventData eventData)
{
get => IsSelected;
set
{
if (m_Mode == ButtonModeType.Toggle)
{
ProcessClick();
}
}
}
#endregion
#region Private Methods
private bool ShouldProcessEvent(PointerEventData eventData)
{ {
return m_Interactable && return m_Interactable &&
eventData.button == PointerEventData.InputButton.Left && 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) if (m_Mode == ButtonModeType.Normal)
{ {
@ -310,142 +223,87 @@ public class UXButton : UIBehaviour, IButton,
} }
else else
{ {
IsSelected = !IsSelected; Selected = !Selected;
} }
} }
private void UpdateVisualState(SelectionState state, bool instant) private void SetState(SelectionState state)
{ {
ProcessTransitionData(m_TransitionData, state, instant); if (m_SelectionState == state) return;
foreach (var transition in m_ChildTransitions) m_SelectionState = state;
{ ApplyVisualState(state, false);
ProcessTransitionData(transition, state, instant);
}
} }
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; ApplyTransition(m_TransitionData, state, instant);
for (int i = 0; i < m_ChildTransitions.Count; i++)
ApplyTransition(m_ChildTransitions[i], state, instant);
}
Color tintColor; private void ApplyTransition(TransitionData data, SelectionState state, bool instant)
Sprite transitionSprite;
string triggerName;
switch (state)
{ {
case SelectionState.Normal: if (data.targetGraphic == null && data.transition != Selectable.Transition.Animation)
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; return;
}
switch (transition.transition) (Color color, Sprite sprite, string trigger) = state switch
{
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)
};
switch (data.transition)
{ {
case Selectable.Transition.ColorTint: case Selectable.Transition.ColorTint:
StartColorTween(transition, tintColor * transition.colors.colorMultiplier, instant); TweenColor(data, color * data.colors.colorMultiplier, instant);
break; break;
case Selectable.Transition.SpriteSwap: case Selectable.Transition.SpriteSwap:
DoSpriteSwap(transition, transitionSprite); SwapSprite(data, sprite);
break; break;
case Selectable.Transition.Animation: case Selectable.Transition.Animation:
TriggerAnimation(triggerName); PlayAnimation(trigger);
break; break;
} }
} }
private void StartColorTween(TransitionData data, Color targetColor, bool instant) private void TweenColor(TransitionData data, Color color, bool instant)
{ {
if (Application.isPlaying) data.targetGraphic.CrossFadeColor(color, instant ? 0f : data.colors.fadeDuration, true, true);
{
data.targetGraphic.CrossFadeColor(
targetColor,
instant ? 0f : data.colors.fadeDuration,
true,
true
);
}
else
{
data.targetGraphic.canvasRenderer.SetColor(targetColor);
}
} }
private void DoSpriteSwap(TransitionData data, Sprite newSprite) private static void SwapSprite(TransitionData data, Sprite sprite)
{ {
if (data.targetGraphic is Image image) if (data.targetGraphic is Image img)
{ img.overrideSprite = sprite;
image.overrideSprite = newSprite;
}
} }
private void TriggerAnimation(string trigger) private void PlayAnimation(string trigger)
{ {
if (animator == null || if (!Animator || !Animator.isActiveAndEnabled || string.IsNullOrEmpty(trigger))
!animator.isActiveAndEnabled ||
!animator.hasBoundPlayables ||
string.IsNullOrEmpty(trigger))
return; return;
foreach (var resetTrigger in _animResetTriggerIDs.Keys) foreach (int id in _animTriggerCache.Values)
{ Animator.ResetTrigger(id);
animator.ResetTrigger(_animTriggerIDs[resetTrigger]);
if (_animTriggerCache.TryGetValue(trigger, out int hash))
Animator.SetTrigger(hash);
} }
if (_animTriggerIDs.TryGetValue(trigger, out int id))
{
animator.SetTrigger(id);
}
}
private void AddTriggerID(string triggerName) private void PlayAudio(AudioClip clip)
{ {
if (!string.IsNullOrEmpty(triggerName)) if (clip && UXComponentExtensionsHelper.AudioHelper != null)
{
int id = Animator.StringToHash(triggerName);
if (!_animTriggerIDs.ContainsKey(triggerName))
{
_animTriggerIDs.Add(triggerName, id);
_animResetTriggerIDs.Add(triggerName, id);
}
}
}
private void PlayButtonSound(AudioClip clip)
{
if (clip == null || UXComponentExtensionsHelper.AudioHelper == null) return;
UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip); UXComponentExtensionsHelper.AudioHelper.PlayAudio(clip);
} }
private IEnumerator OnFinishSubmit() private IEnumerator ResetAfterSubmit()
{ {
yield return _waitTimeFadeDuration; yield return _waitFadeDuration;
UpdateVisualState( SetState(_mTogSelected ? SelectionState.Selected : SelectionState.Normal);
m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal,
false
);
_resetRoutine = null; _resetRoutine = null;
} }

View File

@ -1,17 +1,17 @@
using System.Collections.Generic;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine.EventSystems; using UnityEngine.EventSystems;
[DisallowMultipleComponent] [DisallowMultipleComponent]
public class UXGroup : UIBehaviour public class UXGroup : UIBehaviour
{ {
[SerializeField] private bool m_AllowSwitchOff; [SerializeField] private bool m_AllowSwitchOff;
[ReadOnly, SerializeField] private List<UXButton> m_Buttons = new(); [SerializeField] private List<UXButton> m_Buttons = new();
private UXButton _current; private UXButton _current;
private readonly HashSet<UXButton> _registeredButtons = new(); private readonly HashSet<UXButton> _registered = new();
public UnityEvent<UXButton> onSelectedChanged = new(); public UnityEvent<UXButton> onSelectedChanged = new();
@ -21,169 +21,118 @@ public class UXGroup : UIBehaviour
set set
{ {
m_AllowSwitchOff = value; m_AllowSwitchOff = value;
ValidateGroupState(); ValidateGroup();
} }
} }
protected override void Awake() => ValidateGroup();
protected override void OnDestroy() protected override void OnDestroy()
{ {
foreach (var button in _registeredButtons) foreach (var btn in _registered)
{ if (btn)
if (button) button.IsSelected = false; btn.Selected = false;
} _registered.Clear();
m_Buttons.Clear(); m_Buttons.Clear();
_registeredButtons.Clear();
base.OnDestroy(); base.OnDestroy();
} }
protected override void Awake() => ValidateGroupState();
public void RegisterButton(UXButton button) 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); if (button.Selected)
_registeredButtons.Add(button);
if (button.IsSelected)
{ {
if (_current && _current != button) if (_current && _current != button)
_current.IsSelected = false; _current.Selected = false;
_current = button; _current = button;
} }
ValidateGroupState(); ValidateGroup();
} }
public void UnregisterButton(UXButton button) public void UnregisterButton(UXButton button)
{ {
if (!button || !_registeredButtons.Contains(button)) return; if (!button || !_registered.Remove(button)) return;
m_Buttons.Remove(button); m_Buttons.Remove(button);
_registeredButtons.Remove(button);
button.IsSelected = false;
if (_current == button) if (_current == button)
_current = null; _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) if (m_AllowSwitchOff) SetSelected(null);
SetSelectedButton(null);
} }
else else
{ {
SetSelectedButton(clickedButton); SetSelected(button);
} }
} }
private void SetSelectedButton(UXButton target) private void SetSelected(UXButton target)
{ {
var previous = _current; var previous = _current;
_current = null; _current = null;
foreach (var button in m_Buttons) foreach (var btn in m_Buttons)
{ {
bool shouldSelect = (button == target); bool select = (btn == target);
if (button.IsSelected != shouldSelect) if (btn.Selected != select)
button.IsSelected = shouldSelect; btn.Selected = select;
if (select) _current = btn;
if (shouldSelect)
_current = button;
} }
if (previous != _current) if (previous != _current)
onSelectedChanged?.Invoke(_current); onSelectedChanged?.Invoke(_current);
} }
private void ValidateGroupState() private void ValidateGroup()
{ {
bool hasSelected = _current != null && _current.IsSelected; if (_current != null && _current.Selected) return;
if (!hasSelected && m_Buttons.Count > 0 && !m_AllowSwitchOff)
SetSelectedButton(m_Buttons[0]); 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) foreach (var btn in m_Buttons)
{ if (btn != exclude && btn.Selected)
if (button != exclusion && button.IsSelected)
return true; return true;
}
return false; 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; if (m_Buttons.Count == 0) return;
int start = _current ? m_Buttons.IndexOf(_current) : -1;
int startIndex = _current ? m_Buttons.IndexOf(_current) : -1; int next = FindNextSelectable(start, dir);
int newIndex = FindSelectableIndex(startIndex, -1); if (next >= 0) SetSelected(m_Buttons[next]);
if (newIndex != -1)
{
SetSelectedButton(m_Buttons[newIndex]);
}
} }
private int FindNextSelectable(int startIndex, int dir)
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)
{ {
if (m_Buttons.Count == 0) return -1; if (m_Buttons.Count == 0) return -1;
int buttonCount = m_Buttons.Count - 1; int count = m_Buttons.Count;
int valueIndex = startIndex == -1 ? 0 : startIndex + direction; int index = (startIndex + dir + count) % count;
int fallBackIndex = -1;
valueIndex = valueIndex > buttonCount ? 0 : (valueIndex < 0 ? buttonCount : valueIndex); for (int i = 0; i < count; i++)
if (valueIndex > buttonCount)
{ {
valueIndex = 0; var btn = m_Buttons[index];
} if (btn && btn.isActiveAndEnabled && btn.Interactable)
else if (valueIndex < 0) return index;
{ index = (index + dir + count) % count;
valueIndex = buttonCount;
} }
while (valueIndex != startIndex) return -1;
{
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;
} }
} }

View File

@ -1,7 +1,5 @@
using System; #if INPUTSYSTEM_SUPPORT
using System.Collections.Generic;
using AlicizaX.UI.Runtime; using AlicizaX.UI.Runtime;
using Sirenix.OdinInspector;
using UnityEngine; using UnityEngine;
namespace AlicizaX.UI.Extension.UXComponent.Hotkey namespace AlicizaX.UI.Extension.UXComponent.Hotkey
@ -22,30 +20,8 @@ namespace AlicizaX.UI.Extension.UXComponent.Hotkey
_holderObjectBase.OnWindowShowEvent -= BindHotKeys; _holderObjectBase.OnWindowShowEvent -= BindHotKeys;
_holderObjectBase.OnWindowClosedEvent -= UnBindHotKeys; _holderObjectBase.OnWindowClosedEvent -= UnBindHotKeys;
} }
#if UNITY_EDITOR
[InlineButton("SetHotKeyButtons")]
#endif
[SerializeField] [SerializeField]
[HideLabel] private UXHotkey[] hotButtons;
private UXButton[] hotButtons;
#if UNITY_EDITOR
private void SetHotKeyButtons()
{
var btns = transform.GetComponentsInChildren<UXButton>(true);
var hotBtnList = new List<UXButton>();
for (int i = 0; i < btns.Length; i++)
{
if (btns[i].HasHotKeyRefrenced())
{
hotBtnList.Add(btns[i]);
}
}
hotButtons = hotBtnList.ToArray();
}
#endif
internal void BindHotKeys() internal void BindHotKeys()
{ {
@ -64,3 +40,5 @@ namespace AlicizaX.UI.Extension.UXComponent.Hotkey
} }
} }
} }
#endif

View File

@ -1,174 +1,28 @@
#if INPUTSYSTEM_SUPPORT #if INPUTSYSTEM_SUPPORT
using System;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections.Generic;
using System.Collections;
internal enum EHotkeyPressType namespace AlicizaX.UI.Extension
{ {
Started, public class UXHotkey : MonoBehaviour
Performed
}
internal static class UXHotkeyComponent
{
private readonly struct HotkeyRegistration
{ {
public readonly EHotkeyPressType pressType; [SerializeField] private UXButton _button;
public readonly UXButton button;
public HotkeyRegistration(UXButton btn, EHotkeyPressType pressType) [SerializeField] internal UnityEngine.InputSystem.InputActionReference _hotKeyRefrence;
[SerializeField] internal EHotkeyPressType _hotkeyPressType;
private void OnValidate()
{ {
button = btn; _button = GetComponent<UXButton>();
this.pressType = pressType;
}
} }
private static readonly Dictionary<string, List<HotkeyRegistration>> _hotkeyRegistry = internal void HotkeyActionTrigger()
new Dictionary<string, List<HotkeyRegistration>>(32);
private static readonly Dictionary<string, (System.Action<InputAction.CallbackContext> handler, InputActionReference action)> _sharedHandlers =
new Dictionary<string, (System.Action<InputAction.CallbackContext>, InputActionReference)>(32);
private static readonly Dictionary<UXButton, string> _buttonRegistrations =
new Dictionary<UXButton, string>(64);
#if UNITY_EDITOR
[UnityEditor.Callbacks.DidReloadScripts]
internal static void ClearHotkeyRegistry()
{ {
foreach (var key in _buttonRegistrations.Keys) if (_button.Interactable)
{ {
UnregisterHotkey(key); _button.OnSubmit(null);
}
_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))
{
registrations = new List<HotkeyRegistration>(4);
_hotkeyRegistry[actionId] = registrations;
}
registrations.Add(registration);
_buttonRegistrations[button] = actionId;
if (!_sharedHandlers.ContainsKey(actionId))
{
System.Action<InputAction.CallbackContext> 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(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;
}
}
}
_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 #endif

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 0aa77908962c48199d63710fa15b8c37 guid: 9aa1d0e145a6435c94190809bbc99235
timeCreated: 1754555268 timeCreated: 1760340422

View File

@ -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<string, List<HotkeyRegistration>> _hotkeyRegistry =
new Dictionary<string, List<HotkeyRegistration>>(32);
private static readonly Dictionary<string, (System.Action<InputAction.CallbackContext> handler, InputActionReference action)> _sharedHandlers =
new Dictionary<string, (System.Action<InputAction.CallbackContext>, InputActionReference)>(32);
private static readonly Dictionary<UXHotkey, string> _buttonRegistrations =
new Dictionary<UXHotkey, string>(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<HotkeyRegistration>(4);
_hotkeyRegistry[actionId] = registrations;
}
registrations.Add(registration);
_buttonRegistrations[button] = actionId;
if (!_sharedHandlers.ContainsKey(actionId))
{
System.Action<InputAction.CallbackContext> 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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0aa77908962c48199d63710fa15b8c37
timeCreated: 1754555268