// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System; using System.Collections.Generic; using Doozy.Runtime.Common.Layouts; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.Reactor; using Doozy.Runtime.Reactor.Animations; using Doozy.Runtime.Reactor.Ticker; using Doozy.Runtime.UIManager.Components; using UnityEngine; using UnityEngine.UI; // ReSharper disable UnusedMember.Global // ReSharper disable MemberCanBePrivate.Global namespace Doozy.Runtime.UIManager.Animators { /// /// Specialized animator component used to animate a RectTransform’s position, rotation, scale and alpha /// by listening to a target UISelectable (controller) selection state changes. /// [RequireComponent(typeof(CanvasGroup))] [RequireComponent(typeof(RectTransform))] [AddComponentMenu("Doozy/UI/Components/Animators/UISelectable UIAnimator")] public class UISelectableUIAnimator : BaseUISelectableAnimator { private CanvasGroup m_CanvasGroup; /// Reference to the CanvasGroup component public CanvasGroup canvasGroup => m_CanvasGroup ? m_CanvasGroup : m_CanvasGroup = GetComponent(); [SerializeField] private UIAnimation NormalAnimation; /// Animation for the Normal selection state public UIAnimation normalAnimation => NormalAnimation ?? (NormalAnimation = new UIAnimation(rectTransform)); [SerializeField] private UIAnimation HighlightedAnimation; /// Animation for the Highlighted selection state public UIAnimation highlightedAnimation => HighlightedAnimation ?? (HighlightedAnimation = new UIAnimation(rectTransform)); [SerializeField] private UIAnimation PressedAnimation; /// Animation for the Pressed selection state public UIAnimation pressedAnimation => PressedAnimation ?? (PressedAnimation = new UIAnimation(rectTransform)); [SerializeField] private UIAnimation SelectedAnimation; /// Animation for the Selected selection state public UIAnimation selectedAnimation => SelectedAnimation ?? (SelectedAnimation = new UIAnimation(rectTransform)); [SerializeField] private UIAnimation DisabledAnimation; /// Animation for the Disabled selection state public UIAnimation disabledAnimation => DisabledAnimation ?? (DisabledAnimation = new UIAnimation(rectTransform)); /// Returns TRUE if any animation is active public bool anyAnimationIsActive => normalAnimation.isActive || highlightedAnimation.isActive || pressedAnimation.isActive || selectedAnimation.isActive || disabledAnimation.isActive; private bool isInLayoutGroup { get; set; } private Vector3 localPosition { get; set; } private UIBehaviourHandler uiBehaviourHandler { get; set; } private bool updateStartPositionInLateUpdate { get; set; } private bool playStateAnimationFromLateUpdate { get; set; } /// Get the Animation triggered by the given selection state /// Target selection state public UIAnimation GetAnimation(UISelectionState state) => state switch { UISelectionState.Normal => normalAnimation, UISelectionState.Highlighted => highlightedAnimation, UISelectionState.Pressed => pressedAnimation, UISelectionState.Selected => selectedAnimation, UISelectionState.Disabled => disabledAnimation, _ => throw new ArgumentOutOfRangeException(nameof(state), state, null) }; #if UNITY_EDITOR protected override void Reset() { foreach (UISelectionState state in UISelectable.uiSelectionStates) ResetAnimation(GetAnimation(state)); NormalAnimation.animationType = UIAnimationType.Reset; NormalAnimation.Move.enabled = true; NormalAnimation.Rotate.enabled = true; NormalAnimation.Scale.enabled = true; NormalAnimation.Fade.enabled = true; base.Reset(); } #endif protected override void Awake() { if (!Application.isPlaying) return; animatorInitialized = false; m_RectTransform = GetComponent(); m_CanvasGroup = GetComponent(); UpdateLayout(); } protected override void OnEnable() { if (!Application.isPlaying) return; playStateAnimationFromLateUpdate = true; base.OnEnable(); UpdateLayout(); updateStartPositionInLateUpdate = true; } protected override void OnDisable() { if (!Application.isPlaying) return; base.OnDisable(); RefreshLayout(); } protected override void OnDestroy() { base.OnDestroy(); foreach (UISelectionState state in UISelectable.uiSelectionStates) GetAnimation(state)?.Recycle(); } private void OnRectTransformDimensionsChange() { if (!isConnected) return; if (!isInLayoutGroup) return; updateStartPositionInLateUpdate = true; } private void LateUpdate() { if (!animatorInitialized) return; if (playStateAnimationFromLateUpdate) { if (isConnected) { Play(controller.currentUISelectionState); playStateAnimationFromLateUpdate = false; } } if (!isInLayoutGroup) return; if (!isConnected) return; if (anyAnimationIsActive) return; if (!updateStartPositionInLateUpdate && localPosition == rectTransform.localPosition) return; if (controller.currentUISelectionState != UISelectionState.Normal) return; if (CanvasUpdateRegistry.IsRebuildingLayout()) return; RefreshLayout(); UpdateStartPosition(); } private void UpdateLayout() { isInLayoutGroup = rectTransform.IsInLayoutGroup(); uiBehaviourHandler = null; if (!isInLayoutGroup) return; LayoutGroup layout = rectTransform.GetLayoutGroupInParent(); if (layout == null) return; uiBehaviourHandler = layout.GetUIBehaviourHandler(); System.Diagnostics.Debug.Assert(uiBehaviourHandler != null, nameof(uiBehaviourHandler) + " != null"); uiBehaviourHandler.SetDirty(); } /// Refresh the layout the selectable is in private void RefreshLayout() { if (uiBehaviourHandler == null) return; uiBehaviourHandler.RefreshLayout(); } /// Update the start position of the selectable public void UpdateStartPosition() { foreach (UISelectionState state in UISelectable.uiSelectionStates) { UIAnimation uiAnimation = GetAnimation(state); uiAnimation.startPosition = rectTransform.anchoredPosition3D; if (uiAnimation.Move.isPlaying) uiAnimation.Move.UpdateValues(); } localPosition = rectTransform.localPosition; updateStartPositionInLateUpdate = false; } /// Returns True if the givens selection state is enabled and the animation is not null. /// Selection state public override bool IsStateEnabled(UISelectionState state) => GetAnimation(state).isEnabled; /// Update the animations settings public override void UpdateSettings() { foreach (UISelectionState state in UISelectable.uiSelectionStates) GetAnimation(state).SetTarget(rectTransform, canvasGroup); } /// Stop all animations public override void StopAllReactions() { foreach (UISelectionState state in UISelectable.uiSelectionStates) GetAnimation(state)?.Stop(); } /// Reset all the reactions to their initial values (if the animation is enabled) /// If true, forced will ignore if the animation is enabled or not public override void ResetToStartValues(bool forced = false) { if (normalAnimation.isActive) normalAnimation.Stop(); if (highlightedAnimation.isActive) highlightedAnimation.Stop(); if (pressedAnimation.isActive) pressedAnimation.Stop(); if (selectedAnimation.isActive) selectedAnimation.Stop(); if (disabledAnimation.isActive) disabledAnimation.Stop(); normalAnimation.ResetToStartValues(); highlightedAnimation.ResetToStartValues(); pressedAnimation.ResetToStartValues(); selectedAnimation.ResetToStartValues(); disabledAnimation.ResetToStartValues(); if (m_RectTransform == null) return; rectTransform.anchoredPosition3D = normalAnimation.startPosition; rectTransform.localEulerAngles = normalAnimation.startRotation; rectTransform.localScale = normalAnimation.startScale; canvasGroup.alpha = normalAnimation.startAlpha; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(rectTransform); UnityEditor.SceneView.RepaintAll(); #endif } /// Set animation heartbeat * public override List SetHeartbeat() { var list = new List(); for (int i = 0; i < 20; i++) list.Add(new T()); normalAnimation.Move.SetHeartbeat(list[0]); normalAnimation.Rotate.SetHeartbeat(list[1]); normalAnimation.Scale.SetHeartbeat(list[2]); normalAnimation.Fade.SetHeartbeat(list[3]); highlightedAnimation.Move.SetHeartbeat(list[4]); highlightedAnimation.Rotate.SetHeartbeat(list[5]); highlightedAnimation.Scale.SetHeartbeat(list[6]); highlightedAnimation.Fade.SetHeartbeat(list[7]); pressedAnimation.Move.SetHeartbeat(list[8]); pressedAnimation.Rotate.SetHeartbeat(list[9]); pressedAnimation.Scale.SetHeartbeat(list[10]); pressedAnimation.Fade.SetHeartbeat(list[11]); selectedAnimation.Move.SetHeartbeat(list[12]); selectedAnimation.Rotate.SetHeartbeat(list[13]); selectedAnimation.Scale.SetHeartbeat(list[14]); selectedAnimation.Fade.SetHeartbeat(list[15]); disabledAnimation.Move.SetHeartbeat(list[16]); disabledAnimation.Rotate.SetHeartbeat(list[17]); disabledAnimation.Scale.SetHeartbeat(list[18]); disabledAnimation.Fade.SetHeartbeat(list[19]); return list; } /// Play the animation for the given selection state /// Selection state public override void Play(UISelectionState state) { if (playStateAnimationFromLateUpdate) { GetAnimation(state)?.SetProgressAtOne(); return; } GetAnimation(state)?.Play(); } /// Set a new start position value (RectTransform.anchoredPosition3D) for all state animations /// New start position public void SetStartPosition(Vector3 value) { normalAnimation.startPosition = value; highlightedAnimation.startPosition = value; pressedAnimation.startPosition = value; selectedAnimation.startPosition = value; disabledAnimation.startPosition = value; } /// Set a new start rotation value (RectTransform.localEulerAngles) for all state animations /// New start rotation public void SetStartRotation(Vector3 value) { normalAnimation.startRotation = value; highlightedAnimation.startRotation = value; pressedAnimation.startRotation = value; selectedAnimation.startRotation = value; disabledAnimation.startRotation = value; } /// Set a new start scale value (RectTransform.localScale) for all state animations /// New start scale public void SetStartScale(Vector3 value) { normalAnimation.startScale = value; highlightedAnimation.startScale = value; pressedAnimation.startScale = value; selectedAnimation.startScale = value; disabledAnimation.startScale = value; } /// Set a new start alpha value (CanvasGroup.alpha) for all state animations /// New start scale public void SetStartAlpha(float value) { normalAnimation.startAlpha = value; highlightedAnimation.startAlpha = value; pressedAnimation.startAlpha = value; selectedAnimation.startAlpha = value; disabledAnimation.startAlpha = value; } /// Reset the give animation to a set of default values /// Target animation private static void ResetAnimation(UIAnimation target) { target.Move.Reset(); target.Rotate.Reset(); target.Scale.Reset(); target.Fade.Reset(); target.animationType = UIAnimationType.State; target.Move.fromReferenceValue = ReferenceValue.CurrentValue; target.Rotate.fromReferenceValue = ReferenceValue.CurrentValue; target.Scale.fromReferenceValue = ReferenceValue.CurrentValue; target.Fade.fromReferenceValue = ReferenceValue.CurrentValue; target.Move.settings.duration = UISelectable.k_DefaultAnimationDuration; target.Rotate.settings.duration = UISelectable.k_DefaultAnimationDuration; target.Scale.settings.duration = UISelectable.k_DefaultAnimationDuration; target.Fade.settings.duration = UISelectable.k_DefaultAnimationDuration; } } }