From 39630c4f78daf3da8d8a077709e19555593ddb5c 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, 28 Jul 2025 13:04:49 +0800 Subject: [PATCH] =?UTF-8?q?UXButton=20UXGroup=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/Icons/SwitchOff.png | Bin 3237 -> 864 bytes Editor/Icons/SwitchOffHover.png | Bin 3183 -> 864 bytes Editor/Icons/SwitchOn.png | Bin 4816 -> 884 bytes Editor/Res/TMP - Text Component Icon.psd | Bin 0 -> 89826 bytes Editor/Res/TMP - Text Component Icon.psd.meta | 117 ++++++++ Editor/Res/UIExtensionGUISkin.guiskin | 2 +- Editor/UX/UXButtonEditor.cs | 2 +- Editor/UX/UXGroupEditor.cs | 29 -- Editor/UX/UXGroupEditor.cs.meta | 3 - .../UGUIExtension/Text/UXTextMeshPro.cs.meta | 2 +- Runtime/UGUIExtension/UX/UXButton.cs | 271 +++++++++++------- Runtime/UGUIExtension/UX/UXGroup.cs | 98 +++---- 12 files changed, 339 insertions(+), 185 deletions(-) create mode 100644 Editor/Res/TMP - Text Component Icon.psd create mode 100644 Editor/Res/TMP - Text Component Icon.psd.meta delete mode 100644 Editor/UX/UXGroupEditor.cs delete mode 100644 Editor/UX/UXGroupEditor.cs.meta diff --git a/Editor/Icons/SwitchOff.png b/Editor/Icons/SwitchOff.png index 08842188acc61df7f8e3bb85e8e7f65b70800443..5557bf7290c46d98c587f4551e0f7f9167ef4a9e 100644 GIT binary patch literal 864 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 diff --git a/Editor/Icons/SwitchOffHover.png b/Editor/Icons/SwitchOffHover.png index 8eea37a14f0ab4cfc9a7d76fb312a8a3e795cd73..5557bf7290c46d98c587f4551e0f7f9167ef4a9e 100644 GIT binary patch literal 864 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 bwwKPa1+ns(<#+X#zXTCs6GKbCOv?H{DCwyo diff --git a/Editor/Icons/SwitchOn.png b/Editor/Icons/SwitchOn.png index 9f3f6ddfacf4bde235897a724d86ec1979de78ae..158f6a681796194a86cd0bb0d54a46e3ee5bd215 100644 GIT binary patch literal 884 zcmV-)1B?8LP)Px&E=fc|RCr$PT3vG5Fc97~93Z6^ATy;iJw?n(5+30afkPA?NlpSiMQ1{0;uFxk z*GR1_%RgXa$&ww#7B9>&(n|a7x2ydDq30j$x&Gh)a17`<0?q|`P5>7KoeMY@=s5z; z1$s^Z_X;={&~bsA&F?Y5aI7Oke{UEt8SnES(YNYHi2+}KzB~}bdj#-R4{BpQ1=u6P zoJPZKBfGL-CLl5vC zDDeR>bxXu20)JO!KokPNgd!botx+Cfd7MvHvTnp!>ruK*fuI4w)3gYL3jp|L*vTH& zMmb3Hv~^-jcq_+6|;T~nJr&;@J>f<={@DR|H2h?kEC!8jr|b(pN&k9h`R z*}Ft2TlYkX5Ko2ehy!I^8(9{~tV4*{%Tx(~KrV_60C$vv zk3h${0i;2;Yl%vRr>A|@R9KuN4Z!)u?D%s8yH$&SkkGP@rJHLrz*2pbujK$xhOA>7 z90NLLP-S%-1C$}_*apXdju}*09mfD=$U3&cF`#1xRaU3>4EP63L&LzA6}4Lc0000< KMNUMnLSTZjw0aHz literal 4816 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 LwR(Acqmuj|^O?dr literal 0 HcmV?d00001 diff --git a/Editor/Res/TMP - Text Component Icon.psd.meta b/Editor/Res/TMP - Text Component Icon.psd.meta new file mode 100644 index 0000000..5b8f93b --- /dev/null +++ b/Editor/Res/TMP - Text Component Icon.psd.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: c4398d454b1a861499ef73d23bc7a032 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Res/UIExtensionGUISkin.guiskin b/Editor/Res/UIExtensionGUISkin.guiskin index 9f64856..88fa1e5 100644 --- a/Editor/Res/UIExtensionGUISkin.guiskin +++ b/Editor/Res/UIExtensionGUISkin.guiskin @@ -211,7 +211,7 @@ MonoBehaviour: m_ImagePosition: 0 m_ContentOffset: {x: 15, y: 1} m_FixedWidth: 24 - m_FixedHeight: 15 + m_FixedHeight: 24 m_StretchWidth: 1 m_StretchHeight: 0 m_label: diff --git a/Editor/UX/UXButtonEditor.cs b/Editor/UX/UXButtonEditor.cs index 42a47dd..c95cf92 100644 --- a/Editor/UX/UXButtonEditor.cs +++ b/Editor/UX/UXButtonEditor.cs @@ -9,7 +9,7 @@ using UnityEngine.UI; [CanEditMultipleObjects] [CustomEditor(typeof(UXButton), true)] -public class UXButtonEditor : Editor +internal class UXButtonEditor : Editor { private enum TabType { diff --git a/Editor/UX/UXGroupEditor.cs b/Editor/UX/UXGroupEditor.cs deleted file mode 100644 index b523426..0000000 --- a/Editor/UX/UXGroupEditor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// using UnityEditor; -// -// #if UNITY_EDITOR -// [CustomEditor(typeof(UXGroup))] -// public class UXGroupEditor : Editor -// { -// private SerializedProperty m_AllowSwitchOff; -// private SerializedProperty m_Buttons; -// private SerializedProperty m_OnSelectedChanged; -// -// private void OnEnable() -// { -// m_AllowSwitchOff = serializedObject.FindProperty("m_AllowSwitchOff"); -// m_Buttons = serializedObject.FindProperty("m_Buttons"); -// m_OnSelectedChanged = serializedObject.FindProperty("onSelectedChanged"); -// } -// -// public override void OnInspectorGUI() -// { -// serializedObject.Update(); -// -// EditorGUILayout.PropertyField(m_AllowSwitchOff); -// EditorGUILayout.PropertyField(m_Buttons, true); -// EditorGUILayout.PropertyField(m_OnSelectedChanged); -// -// serializedObject.ApplyModifiedProperties(); -// } -// } -// #endif diff --git a/Editor/UX/UXGroupEditor.cs.meta b/Editor/UX/UXGroupEditor.cs.meta deleted file mode 100644 index 3bbd213..0000000 --- a/Editor/UX/UXGroupEditor.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: d66e1f78170d455c93d71e71ee8f735a -timeCreated: 1744275087 \ No newline at end of file diff --git a/Runtime/UGUIExtension/Text/UXTextMeshPro.cs.meta b/Runtime/UGUIExtension/Text/UXTextMeshPro.cs.meta index 121427c..a1339a5 100644 --- a/Runtime/UGUIExtension/Text/UXTextMeshPro.cs.meta +++ b/Runtime/UGUIExtension/Text/UXTextMeshPro.cs.meta @@ -5,7 +5,7 @@ MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 - icon: {instanceID: 0} + icon: {fileID: 2800000, guid: c4398d454b1a861499ef73d23bc7a032, type: 3} userData: assetBundleName: assetBundleVariant: diff --git a/Runtime/UGUIExtension/UX/UXButton.cs b/Runtime/UGUIExtension/UX/UXButton.cs index c310e89..20319f3 100644 --- a/Runtime/UGUIExtension/UX/UXButton.cs +++ b/Runtime/UGUIExtension/UX/UXButton.cs @@ -1,11 +1,11 @@ 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; using AudioType = AlicizaX.Audio.Runtime.AudioType; @@ -16,7 +16,6 @@ public enum ButtonModeType Toggle } - [System.Serializable] public class TransitionData { @@ -24,102 +23,130 @@ public class TransitionData public Selectable.Transition transition = Selectable.Transition.ColorTint; public ColorBlock colors; public SpriteState spriteState; - public AnimationTriggers animationTriggers = new AnimationTriggers(); + public AnimationTriggers animationTriggers = new(); } - internal enum SelectionState { Normal, - Highlighted, - Pressed, - Selected, Disabled, } - +[ExecuteInEditMode] [DisallowMultipleComponent] public class UXButton : UIBehaviour, IButton, - IPointerDownHandler, - IPointerUpHandler, - IPointerEnterHandler, - IPointerExitHandler, - IPointerClickHandler + IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, + IPointerExitHandler, IPointerClickHandler, ISubmitHandler { + #region Serialized Fields + [SerializeField] private bool m_Interactable = true; - [SerializeField] private ButtonModeType m_Mode; - - [SerializeField] private Button.ButtonClickedEvent m_OnClick = new Button.ButtonClickedEvent(); - - [SerializeField] private TransitionData m_TransitionData = new TransitionData(); - - [SerializeField] private List m_ChildTransitions = new List(); + [SerializeField] private Button.ButtonClickedEvent m_OnClick = new(); + [SerializeField] private TransitionData m_TransitionData = new(); + [SerializeField] private List m_ChildTransitions = new(); [SerializeField] private UXGroup m_UXGroup; - [SerializeField] private AudioClip hoverAudioClip; [SerializeField] private AudioClip clickAudioClip; + #endregion + + #region Private Variables + [SerializeField] private SelectionState m_SelectionState = SelectionState.Normal; private bool m_DownAndExistUI; private bool m_IsDown; private bool m_IsTogSelected; - private Animator _animator; + private WaitForSeconds _waitTimeFadeDuration; + private Coroutine _resetRoutine; + private bool _boardEvent; + + private readonly Dictionary _animTriggerIDs = new(); + private readonly Dictionary _animResetTriggerIDs = new(); + + #endregion + + #region Properties internal Animator animator { get { - _animator = _animator ?? GetComponent(); + if (!_animator) + _animator = GetComponent(); return _animator; } } public bool IsSelected { - get { return m_IsTogSelected; } + get => m_IsTogSelected; internal set { + if (m_IsTogSelected == value) return; m_IsTogSelected = value; onValueChanged?.Invoke(m_IsTogSelected); - m_SelectionState = m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal; + m_SelectionState = value ? SelectionState.Selected : SelectionState.Normal; UpdateVisualState(m_SelectionState, false); } } public Button.ButtonClickedEvent onClick { - get { return m_OnClick; } - set { m_OnClick = value; } + get => m_OnClick; + set => m_OnClick = value; } - [SerializeField] private UnityEvent m_OnValueChanged = new UnityEvent(); + [SerializeField] private UnityEvent m_OnValueChanged = new(); public UnityEvent onValueChanged { - get { return m_OnValueChanged; } - set { m_OnValueChanged = value; } + get => m_OnValueChanged; + set => m_OnValueChanged = value; } + #endregion + + #region Unity Lifecycle + protected override void Awake() { base.Awake(); - if (m_Mode == ButtonModeType.Toggle) - { - onValueChanged?.Invoke(IsSelected); - } + _waitTimeFadeDuration = new WaitForSeconds( + Mathf.Max(0.01f, m_TransitionData.colors.fadeDuration)); + + 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() + { + if (_resetRoutine != null) + { + StopCoroutine(_resetRoutine); + _resetRoutine = null; + } + + base.OnDestroy(); + } + + #endregion + + #region Event Handlers void IPointerDownHandler.OnPointerDown(PointerEventData eventData) { - if (!m_Interactable) return; - if (eventData.button != PointerEventData.InputButton.Left) return; + if (!ShouldProcessEvent(eventData)) return; + m_IsDown = true; m_SelectionState = SelectionState.Pressed; UpdateVisualState(m_SelectionState, false); @@ -127,29 +154,30 @@ public class UXButton : UIBehaviour, IButton, void IPointerUpHandler.OnPointerUp(PointerEventData eventData) { - if (!m_Interactable) return; + if (!m_Interactable || eventData.button != PointerEventData.InputButton.Left) + return; m_IsDown = false; - - if (!m_IsTogSelected) + if (m_IsTogSelected) { - m_SelectionState = m_DownAndExistUI ? SelectionState.Normal : SelectionState.Highlighted; - UpdateVisualState(m_SelectionState, false); + m_SelectionState = SelectionState.Selected; } else { - m_SelectionState = SelectionState.Selected; - UpdateVisualState(m_SelectionState, false); + m_SelectionState = m_DownAndExistUI ? SelectionState.Normal : SelectionState.Highlighted; } + + UpdateVisualState(m_SelectionState, false); } void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData) { - if (!m_Interactable || CantTouch()) return; + if (!ShouldProcessEvent(eventData)) return; - m_SelectionState = SelectionState.Highlighted; m_DownAndExistUI = false; if (m_IsDown) return; + + m_SelectionState = SelectionState.Highlighted; UpdateVisualState(m_SelectionState, false); PlayButtonSound(hoverAudioClip); } @@ -163,45 +191,72 @@ public class UXButton : UIBehaviour, IButton, return; } - if (CantTouch()) - { - return; - } - - m_SelectionState = SelectionState.Normal; - + m_SelectionState = IsSelected ? SelectionState.Selected : SelectionState.Normal; UpdateVisualState(m_SelectionState, false); } - private bool CantTouch() + void IPointerClickHandler.OnPointerClick(PointerEventData eventData) { - return m_Mode == ButtonModeType.Toggle && m_IsTogSelected; + if (eventData.button != PointerEventData.InputButton.Left || !m_Interactable) + return; + + PlayButtonSound(clickAudioClip); + ProcessClick(); } + void ISubmitHandler.OnSubmit(BaseEventData eventData) + { + UpdateVisualState(SelectionState.Pressed, false); + ProcessClick(); + + if (_resetRoutine != null) + StopCoroutine(OnFinishSubmit()); + + _resetRoutine = StartCoroutine(OnFinishSubmit()); + } + + #endregion + + #region Public Methods + public void SetSelect(bool state, bool boardEvent = false) { if (m_Mode != ButtonModeType.Toggle) return; - m_IsTogSelected = state; - if (boardEvent) onValueChanged?.Invoke(m_IsTogSelected); - m_SelectionState = m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal; - UpdateVisualState(m_SelectionState, false); + _boardEvent = boardEvent; + IsSelected = state; + } + + #endregion + + #region Private Methods + + private bool ShouldProcessEvent(PointerEventData eventData) + { + return m_Interactable && + eventData.button == PointerEventData.InputButton.Left && + !(m_Mode == ButtonModeType.Toggle && IsSelected); } private void ProcessClick() { + if (!_boardEvent) + { + _boardEvent = true; + return; + } + + _boardEvent = true; if (m_Mode == ButtonModeType.Normal) { UISystemProfilerApi.AddMarker("Button.onClick", this); - onClick?.Invoke(); + m_OnClick?.Invoke(); + } + else if (m_UXGroup) + { + m_UXGroup.NotifyButtonClicked(this); } else { - if (m_UXGroup) - { - m_UXGroup.NotifyButtonClicked(this); - return; - } - IsSelected = !IsSelected; } } @@ -215,7 +270,6 @@ public class UXButton : UIBehaviour, IButton, } } - private void ProcessTransitionData(TransitionData transition, SelectionState state, bool instant) { if (transition.targetGraphic == null) return; @@ -223,11 +277,12 @@ public class UXButton : UIBehaviour, IButton, Color tintColor; Sprite transitionSprite; string triggerName; + switch (state) { case SelectionState.Normal: tintColor = transition.colors.normalColor; - transitionSprite = null; + transitionSprite = transition.spriteState.highlightedSprite; triggerName = transition.animationTriggers.normalTrigger; break; case SelectionState.Highlighted: @@ -251,10 +306,7 @@ public class UXButton : UIBehaviour, IButton, triggerName = transition.animationTriggers.disabledTrigger; break; default: - tintColor = Color.black; - transitionSprite = null; - triggerName = string.Empty; - break; + return; } switch (transition.transition) @@ -266,60 +318,83 @@ public class UXButton : UIBehaviour, IButton, DoSpriteSwap(transition, transitionSprite); break; case Selectable.Transition.Animation: - TriggerAnimation(transition.animationTriggers, triggerName); + TriggerAnimation(triggerName); break; } } - protected void StartColorTween(TransitionData transitionData, Color targetColor, bool instant) + private void StartColorTween(TransitionData data, Color targetColor, bool instant) { if (Application.isPlaying) { - transitionData.targetGraphic.CrossFadeColor(targetColor, instant ? 0f : transitionData.colors.fadeDuration, true, true); + data.targetGraphic.CrossFadeColor( + targetColor, + instant ? 0f : data.colors.fadeDuration, + true, + true + ); } else { - transitionData.targetGraphic.canvasRenderer.SetColor(targetColor); + data.targetGraphic.canvasRenderer.SetColor(targetColor); } } - protected void DoSpriteSwap(TransitionData transitionData, Sprite newSprite) + private void DoSpriteSwap(TransitionData data, Sprite newSprite) { - if (transitionData.targetGraphic is Image image) + if (data.targetGraphic is Image image) { image.overrideSprite = newSprite; } - else if (transitionData.targetGraphic != null) + } + + private void TriggerAnimation(string trigger) + { + if (animator == null || + !animator.isActiveAndEnabled || + !animator.hasBoundPlayables || + string.IsNullOrEmpty(trigger)) + return; + + foreach (var resetTrigger in _animResetTriggerIDs.Keys) { - Log.Error($"Target Graphic must be Image for SpriteSwap. Object: {transitionData.targetGraphic.name}"); + animator.ResetTrigger(_animTriggerIDs[resetTrigger]); + } + + if (_animTriggerIDs.TryGetValue(trigger, out int id)) + { + animator.SetTrigger(id); } } - void TriggerAnimation(AnimationTriggers animationTriggers, string triggername) + private void AddTriggerID(string triggerName) { - if (animator == null || !animator.isActiveAndEnabled || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername)) - return; - - animator.ResetTrigger(animationTriggers.normalTrigger); - animator.ResetTrigger(animationTriggers.highlightedTrigger); - animator.ResetTrigger(animationTriggers.pressedTrigger); - animator.ResetTrigger(animationTriggers.selectedTrigger); - animator.ResetTrigger(animationTriggers.disabledTrigger); - - animator.SetTrigger(triggername); + if (!string.IsNullOrEmpty(triggerName)) + { + int id = Animator.StringToHash(triggerName); + if (!_animTriggerIDs.ContainsKey(triggerName)) + { + _animTriggerIDs.Add(triggerName, id); + _animResetTriggerIDs.Add(triggerName, id); + } + } } - protected void PlayButtonSound(AudioClip clip) + private void PlayButtonSound(AudioClip clip) { - GameApp.Audio?.Play(AudioType.UISound, clip, false, GameApp.Audio.UISoundVolume); + if (clip == null || GameApp.Audio == null) return; + GameApp.Audio.Play(AudioType.UISound, clip, false, GameApp.Audio.UISoundVolume); } - - public void OnPointerClick(PointerEventData eventData) + private IEnumerator OnFinishSubmit() { - if (eventData.button != PointerEventData.InputButton.Left) - return; - PlayButtonSound(clickAudioClip); - ProcessClick(); + yield return _waitTimeFadeDuration; + UpdateVisualState( + m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal, + false + ); + _resetRoutine = null; } + + #endregion } diff --git a/Runtime/UGUIExtension/UX/UXGroup.cs b/Runtime/UGUIExtension/UX/UXGroup.cs index 286cdbb..297c452 100644 --- a/Runtime/UGUIExtension/UX/UXGroup.cs +++ b/Runtime/UGUIExtension/UX/UXGroup.cs @@ -1,5 +1,3 @@ -// UXGroup.cs - using UnityEngine; using UnityEngine.Events; using System.Collections.Generic; @@ -10,11 +8,12 @@ using UnityEngine.EventSystems; public class UXGroup : UIBehaviour { [SerializeField] private bool m_AllowSwitchOff; - [ReadOnly] [SerializeField] private List m_Buttons = new List(); + [ReadOnly, SerializeField] private List m_Buttons = new(); - private UXButton currentUXButton = null; + private UXButton _current; + private readonly HashSet _registeredButtons = new(); - public UnityEvent onSelectedChanged = new UnityEvent(); + public UnityEvent onSelectedChanged = new(); public bool allowSwitchOff { @@ -28,87 +27,83 @@ public class UXGroup : UIBehaviour protected override void OnDestroy() { - base.OnDestroy(); + foreach (var button in _registeredButtons) + { + if (button) button.IsSelected = false; + } m_Buttons.Clear(); - currentUXButton = null; + _registeredButtons.Clear(); + base.OnDestroy(); } - - protected override void Awake() - { - base.Awake(); - ValidateGroupState(); - } + protected override void Awake() => ValidateGroupState(); public void RegisterButton(UXButton button) { - if (!m_Buttons.Contains(button)) + if (!button || _registeredButtons.Contains(button)) return; + + m_Buttons.Add(button); + _registeredButtons.Add(button); + + if (button.IsSelected) { - m_Buttons.Add(button); - if (button.IsSelected) - { - if (currentUXButton != null && currentUXButton != button) - { - currentUXButton.IsSelected = false; - } - currentUXButton = button; - } - ValidateGroupState(); + if (_current && _current != button) + _current.IsSelected = false; + _current = button; } + + ValidateGroupState(); } public void UnregisterButton(UXButton button) { - if (m_Buttons.Contains(button)) - { - m_Buttons.Remove(button); - button.IsSelected = false; - } + if (!button || !_registeredButtons.Contains(button)) return; + + m_Buttons.Remove(button); + _registeredButtons.Remove(button); + button.IsSelected = false; + + if (_current == button) + _current = null; } internal void NotifyButtonClicked(UXButton clickedButton) { - if (!clickedButton.IsSelected) - { - SetSelectedButton(clickedButton); - } - else + if (clickedButton.IsSelected) { if (m_AllowSwitchOff) SetSelectedButton(null); - else if (currentUXButton != clickedButton) - clickedButton.IsSelected = true; + } + else + { + SetSelectedButton(clickedButton); } } - private void SetSelectedButton(UXButton targetButton) + private void SetSelectedButton(UXButton target) { - UXButton previousSelected = currentUXButton; - currentUXButton = null; // 防止递归 + var previous = _current; + _current = null; foreach (var button in m_Buttons) { - bool shouldSelect = (button == targetButton); + bool shouldSelect = (button == target); if (button.IsSelected != shouldSelect) - { button.IsSelected = shouldSelect; - } - if (shouldSelect) currentUXButton = button; + + if (shouldSelect) + _current = button; } - if (previousSelected != currentUXButton) - { - onSelectedChanged.Invoke(currentUXButton); - } + if (previous != _current) + onSelectedChanged?.Invoke(_current); } private void ValidateGroupState() { - bool anySelected = m_Buttons.Exists(b => b.IsSelected); - if (!anySelected && m_Buttons.Count > 0 && !m_AllowSwitchOff) - { + bool hasSelected = _current != null && _current.IsSelected; + if (!hasSelected && m_Buttons.Count > 0 && !m_AllowSwitchOff) SetSelectedButton(m_Buttons[0]); - } } public bool AnyOtherSelected(UXButton exclusion) @@ -118,7 +113,6 @@ public class UXGroup : UIBehaviour if (button != exclusion && button.IsSelected) return true; } - return false; } }