// 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; using System.Collections.Generic; using System.Linq; using Doozy.Runtime.Common.Extensions; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.Global; using Doozy.Runtime.Mody; using Doozy.Runtime.Reactor; using Doozy.Runtime.Reactor.Internal; using Doozy.Runtime.Reactor.Reactions; using Doozy.Runtime.UIManager.Events; using Doozy.Runtime.UIManager.Input; using Doozy.Runtime.UIManager.ScriptableObjects; using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI; // ReSharper disable MemberCanBeProtected.Global // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Local // ReSharper disable VirtualMemberNeverOverridden.Global // ReSharper disable UnusedMember.Global namespace Doozy.Runtime.UIManager.Containers { /// /// Basic container with show and hide capabilities. /// All other containers use this as their base. /// [RequireComponent(typeof(Canvas))] // [RequireComponent(typeof(GraphicRaycaster))] [RequireComponent(typeof(RectTransform))] [AddComponentMenu("Doozy/UI/Containers/UIContainer")] [SelectionBase] public class UIContainer : MonoBehaviour, ICanvasElement, IUseMultiplayerInfo { #if UNITY_EDITOR [UnityEditor.MenuItem("GameObject/Doozy/UI/Containers/UIContainer", false, 8)] private static void CreateComponent(UnityEditor.MenuCommand menuCommand) { GameObjectUtils.AddToScene("UIContainer", false, true); } #endif /// Stream category name public const string k_StreamCategory = nameof(UIContainer); /// Default animation duration public const float k_DefaultAnimationDuration = 0.3f; #region MultiplayerInfo [SerializeField] private MultiplayerInfo MultiplayerInfo; /// Reference to the MultiPlayerInfo component public MultiplayerInfo multiplayerInfo => MultiplayerInfo; /// Check if a MultiplayerInfo has been referenced public bool hasMultiplayerInfo => multiplayerInfo != null; /// Player index for this component public int playerIndex => multiplayerMode & hasMultiplayerInfo ? multiplayerInfo.playerIndex : inputSettings.defaultPlayerIndex; /// Set the a reference to a MultiplayerInfo /// MultiplayerInfo reference public void SetMultiplayerInfo(MultiplayerInfo reference) => MultiplayerInfo = reference; #endregion /// Reference to the UIManager Input Settings public static UIManagerInputSettings inputSettings => UIManagerInputSettings.instance; /// Check if Multiplayer Mode is enabled public static bool multiplayerMode => inputSettings.multiplayerMode; private Canvas m_Canvas; /// Reference to the Canvas component attached to this GameObject public Canvas canvas => m_Canvas ? m_Canvas : m_Canvas = GetComponent(); private CanvasGroup m_CanvasGroup; /// Reference to the CanvasGroup component attached to this GameObject public CanvasGroup canvasGroup => m_CanvasGroup ? m_CanvasGroup : m_CanvasGroup = GetComponent(); private GraphicRaycaster m_GraphicRaycaster; /// Reference to the GraphicRaycaster component attached to this GameObject public GraphicRaycaster graphicRaycaster => m_GraphicRaycaster ? m_GraphicRaycaster : m_GraphicRaycaster = GetComponent(); private RectTransform m_RectTransform; /// Reference to the RectTransform component attached to this GameObject public RectTransform rectTransform => m_RectTransform ? m_RectTransform : m_RectTransform = GetComponent(); /// Flag used to determine if a Canvas component is attached to this GameObject public bool hasCanvas { get; private set; } /// Flag used to determine if a CanvasGroup component is attached to this GameObject public bool hasGraphicRaycaster { get; private set; } /// Flag used to determine if a CanvasGroup component is attached to this GameObject public bool hasCanvasGroup { get; private set; } /// Behaviour on Start public ContainerBehaviour OnStartBehaviour = ContainerBehaviour.Disabled; #region Visibility private int m_LastFrameVisibilityStateChanged; /// Current visibility state protected VisibilityState VisibilityState = VisibilityState.Visible; /// Current visibility state public VisibilityState visibilityState { get => VisibilityState; private set => SetVisibility(value, true); } /// Visibility state is Visible public bool isVisible => visibilityState == VisibilityState.Visible; /// Visibility state is Hidden public bool isHidden => visibilityState == VisibilityState.Hidden; /// Visibility state is IsShowing - Show animation is running public bool isShowing => visibilityState == VisibilityState.IsShowing; /// Visibility state is IsHiding - Hide animation is running public bool isHiding => visibilityState == VisibilityState.IsHiding; /// Visibility state is either IsShowing or IsHiding - either Show or Hide animation is running public bool inTransition => isShowing || isHiding; /// Show animation started - executed when visibility state changed to IsShowing public ModyEvent OnShowCallback; /// Returns TRUE if the OnShowCallback event is not null and has at least one listener public bool hasOnShowCallbacks => OnShowCallback != null && OnShowCallback.hasCallbacks; /// Visible - Show animation finished - executed when visibility state changed to Visible public ModyEvent OnVisibleCallback; /// Returns TRUE if the OnVisibleCallback event is not null and has at least one listener public bool hasOnVisibleCallbacks => OnVisibleCallback != null && OnVisibleCallback.hasCallbacks; /// Hide animation started - executed when visibility state changed to IsHiding public ModyEvent OnHideCallback; /// Returns TRUE if the OnHideCallback event is not null and has at least one listener public bool hasOnHideCallbacks => OnHideCallback != null && OnHideCallback.hasCallbacks; /// Hidden - Hide animation finished - callback invoked when visibility state changed to Hidden public ModyEvent OnHiddenCallback; /// Returns TRUE if the OnHiddenCallback event is not null and has at least one listener public bool hasOnHiddenCallbacks => OnHiddenCallback != null && OnHiddenCallback.hasCallbacks; /// Visibility changed - callback invoked when visibility state changed public VisibilityStateEvent OnVisibilityChangedCallback; /// Returns TRUE if the OnVisibilityChangedCallback event is not null and has at least one listener public bool hasOnVisibilityChangedCallbacks => OnVisibilityChangedCallback != null && OnVisibilityChangedCallback.GetPersistentEventCount() > 0; /// Returns TRUE if any of the available callbacks has at least one listener public bool hasCallbacks => hasOnShowCallbacks | hasOnVisibleCallbacks | hasOnHideCallbacks | hasOnHiddenCallbacks | hasOnVisibilityChangedCallbacks; [SerializeField] private List ShowProgressors; /// Progressors triggered on Show. Plays forward. public List showProgressors => ShowProgressors ?? (ShowProgressors = new List()); [SerializeField] private List HideProgressors; /// Progressors triggered on Hide. Plays forward. public List hideProgressors => HideProgressors ?? (HideProgressors = new List()); [SerializeField] private List ShowHideProgressors; /// Progressors triggered on both Show and Hide. Plays forward on Show and in reverse on Hide. public List showHideProgressors => ShowHideProgressors ?? (ShowHideProgressors = new List()); /// Action invoked every time before the container needs to change its state public UnityAction showHideExecute { get; set; } /// Flag to keep track if the first show/hide command has been issued public bool executedFirstCommand { get; protected set; } /// Keeps track of the previously executed show/hide command public ShowHideExecute previouslyExecutedCommand { get; protected set; } #endregion /// AnchoredPosition3D to snap to on Awake public Vector3 CustomStartPosition; /// If TRUE, the rectTransform.anchoredPosition3D will 'snap' to the CustomStartPosition on Awake public bool UseCustomStartPosition; /// If TRUE, after this container gets shown, it will get automatically hidden after the AutoHideAfterShowDelay time interval has passed public bool AutoHideAfterShow; /// If AutoHideAfterShow is TRUE, this is the time interval after which this container will get automatically hidden public float AutoHideAfterShowDelay; /// If TRUE, when this container gets hidden, the GameObject this container component is attached to, will be disabled public bool DisableGameObjectWhenHidden; /// If TRUE, when this container gets hidden, the Canvas component found on the same GameObject this container component is attached to, will be disabled public bool DisableCanvasWhenHidden = true; /// If TRUE, when this container gets hidden, the GraphicRaycaster component found on the same GameObject this container component is attached to, will be disabled public bool DisableGraphicRaycasterWhenHidden = true; /// Controls the Block Raycasts property of the target CanvasGroup component public bool HandleCanvasGroupBlockRaycasts = true; /// If TRUE, when this container is shown, any GameObject that is selected by the EventSystem.current will get deselected public bool ClearSelectedOnShow; /// If TRUE, when this container is hidden, any GameObject that is selected by the EventSystem.current will get deselected public bool ClearSelectedOnHide; /// If TRUE, after this container has been shown, the referenced selectable GameObject will get automatically selected by EventSystem.current public bool AutoSelectAfterShow; /// Reference to the GameObject that should be selected after this container has been shown. Works only if AutoSelectAfterShow is TRUE public GameObject AutoSelectTarget; /// Check if there are any referenced Show reactions public bool hasShowReactions => showReactions != null && showReactions.Count > 0; /// Check if there are any referenced Hide reactions public bool hasHideReactions => hideReactions != null && hideReactions.Count > 0; /// Check if any Show animation is active (running) public bool anyShowAnimationIsActive => showReactions.Any(show => show.isActive); /// Check if any Hide animation is active (running) public bool anyHideAnimationIsActive => hideReactions.Any(hide => hide.isActive); /// Check if any Show or Hide animation is active (running) public bool anyAnimationIsActive => anyShowAnimationIsActive | anyHideAnimationIsActive; /// Check if there are any referenced Show progressors public bool hasShowProgressors => showProgressors.Count > 0; /// Check if there are any referenced Hide progressors public bool hasHideProgressors => hideProgressors.Count > 0; /// Check if there are any referenced ShowHide progressors public bool hasShowHideProgressors => showHideProgressors.Count > 0; /// Check if there are any referenced progressors public bool hasProgressors => hasShowProgressors || hasHideProgressors || hasShowHideProgressors; /// Check if any referenced Show progressor is active (running) public bool anyShowProgressorIsActive => showProgressors.Where(p => p != null).Any(p => p.reaction.isActive); /// Check if any referenced Hide progressor is active (running) public bool anyHideProgressorIsActive => hideProgressors.Where(p => p != null).Any(p => p.reaction.isActive); /// Check if any referenced ShowHide progressor is active (running) public bool anyShowHideProgressorIsActive => showHideProgressors.Where(p => p != null).Any(p => p.reaction.isActive); /// Check if any referenced progressor is active (running) public bool anyProgressorIsActive => anyShowProgressorIsActive | anyHideProgressorIsActive | anyShowHideProgressorIsActive; private HashSet m_ShowReactions; /// /// Collection of reactions triggered by Show. /// This collection is dynamically generated at runtime. /// It is populated by all the animators controlled by this UIContainer. /// The animators automatically add/remove their reactions to/from this collection. /// internal HashSet showReactions => m_ShowReactions ??= new HashSet(); /// /// Get the maximum duration for the Show animations Max(startDelay) + Max(duration). /// At start this value can be calculated only after 2 frames have passed (the time it takes for the reactions to register) /// For reactions that use random intervals for startDelay and/or duration, the maximum interval values are taken into account /// public float totalDurationForShow => CalculateTotalShowDuration(); private HashSet m_HideReactions; /// /// Collection of reactions triggered by Hide. /// This collection is dynamically generated at runtime. /// It is populated by all the animators controlled by this UIContainer. /// The animators automatically add/remove their reactions to/from this collection. /// internal HashSet hideReactions => m_HideReactions ??= new HashSet(); /// /// Get the maximum duration for the Hide animations Max(startDelay) + Max(duration). /// At start this value can be calculated only after 2 frames have passed (the time it takes for the reactions to register) /// For reactions that use random intervals for startDelay and/or duration, the maximum interval values are taken into account /// public float totalDurationForHide => CalculateTotalHideDuration(); /// Coroutine used when auto hide is enabled to hide the container after the specified delay private Coroutine m_AutoHideCoroutine; /// Coroutine that is running when the container is showing private Coroutine m_CoroutineIsShowing; /// Coroutine that is running when the container is hiding private Coroutine m_CoroutineIsHiding; /// Coroutine used to disable the GameObject with a delay after the is hidden is completed private Coroutine m_DisableGameObjectWithDelayCoroutine; /// Coroutine used to call Show with a 2 frame delay private Coroutine m_DelayedShowCoroutine; /// Coroutine used to call Hide with a 2 frame delay private Coroutine m_DelayedHideCoroutine; #if ENABLE_INPUT_SYSTEM /// /// Reference to a MultiplayerEventSystem component that will be used to handle the input events if multiplayer mode is enabled. /// It only works with the new Input System. /// For this to work, the MultiplayerEventSystem component should me placed on the same GameObject as the MultiplayerInfo component. /// protected UnityEngine.InputSystem.UI.MultiplayerEventSystem MultiplayerEventSystem; #endif public UIContainer() { UseCustomStartPosition = true; OnShowCallback = new ModyEvent(nameof(OnShowCallback)); OnVisibleCallback = new ModyEvent(nameof(OnVisibleCallback)); OnHideCallback = new ModyEvent(nameof(OnHideCallback)); OnHiddenCallback = new ModyEvent(nameof(OnHiddenCallback)); OnVisibilityChangedCallback = new VisibilityStateEvent(); } #if UNITY_EDITOR protected virtual void OnValidate() { if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying) CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); } #endif // if UNITY_EDITOR #region ICanvasElement public virtual void Rebuild(CanvasUpdate executing) {} public virtual void LayoutComplete() {} public virtual void GraphicUpdateComplete() {} public bool IsDestroyed() => this == null; #endregion protected virtual void Awake() { if (!Application.isPlaying) return; BackButton.Initialize(); // m_Canvas = GetComponent(); // m_GraphicRaycaster = GetComponent(); hasCanvas = GetComponent() != null; hasGraphicRaycaster = GetComponent() != null; hasCanvasGroup = GetComponent() != null; showReactions.Remove(null); hideReactions.Remove(null); executedFirstCommand = false; if (UseCustomStartPosition) { SetCustomStartPosition(CustomStartPosition); } } protected virtual void OnEnable() { if (!Application.isPlaying) return; BackButton.Initialize(); hasCanvas = GetComponent() != null; hasGraphicRaycaster = GetComponent() != null; hasCanvasGroup = GetComponent() != null; showReactions.Remove(null); hideReactions.Remove(null); } protected virtual void Start() { if (!Application.isPlaying) return; RunBehaviour(OnStartBehaviour); } protected virtual void OnDisable() { if (!Application.isPlaying) return; StopIsShowingCoroutine(); StopIsHidingCoroutine(); showReactions.Remove(null); foreach (Reaction reaction in showReactions) reaction.Stop(); hideReactions.Remove(null); foreach (Reaction reaction in hideReactions) reaction.Stop(); StopAllCoroutines(); } protected virtual void OnDestroy() {} /// /// Set the custom start position for the UIContainer. /// /// The new start position /// If true, the UIContainer will be immediately moved to the new position public virtual void SetCustomStartPosition(Vector3 startPosition, bool jumpToPosition = true) { CustomStartPosition = startPosition; showReactions.Remove(null); foreach (Reaction reaction in showReactions) if (reaction is UIMoveReaction moveReaction) moveReaction.startPosition = startPosition; hideReactions.Remove(null); foreach (Reaction reaction in hideReactions) if (reaction is UIMoveReaction moveReaction) moveReaction.startPosition = startPosition; if (jumpToPosition) rectTransform.anchoredPosition3D = startPosition; } /// /// Set the given GameObject as the selected object in the EventSystem or MultiplayerEventSystem, depending on the current input system and if multiplayer mode is enabled. /// /// The GameObject to select protected virtual void SetSelected(GameObject selectable) { #if ENABLE_INPUT_SYSTEM // if the new input system is enabled if ( multiplayerMode && // multiplayer mode is enabled ( MultiplayerEventSystem != null || // multiplayer event system is already set ( hasMultiplayerInfo && // this UIContainer has multiplayer info multiplayerInfo.gameObject.TryGetComponent(out MultiplayerEventSystem) // multiplayer event system is found ) ) ) { MultiplayerEventSystem.SetSelectedGameObject(selectable); // set selected game object in the multiplayer event system } else { EventSystem.current.SetSelectedGameObject(selectable); // set selected game object in the default event system } #elif ENABLE_LEGACY_INPUT_MANAGER // if the old input system is enabled EventSystem.current.SetSelectedGameObject(selectable); // set selected game object in the default event system #endif } private void ExecutedCommand(ShowHideExecute command) { showHideExecute?.Invoke(command); executedFirstCommand = true; if (!hasProgressors) { previouslyExecutedCommand = command; return; } showProgressors.RemoveNulls(); hideProgressors.RemoveNulls(); showHideProgressors.RemoveNulls(); // ReSharper disable Unity.NoNullPropagation switch (command) { case ShowHideExecute.Show: hideProgressors.ForEach(p => p.Stop()); showProgressors.ForEach(p => { p.SetProgressAtZero(); p.Play(PlayDirection.Forward); }); showHideProgressors.ForEach(p => { p.SetProgressAtZero(); p.Play(PlayDirection.Forward); }); break; case ShowHideExecute.Hide: showProgressors.ForEach(p => p.Stop()); hideProgressors.ForEach(p => { p.SetProgressAtZero(); p.Play(PlayDirection.Forward); }); showHideProgressors.ForEach(p => { p.SetProgressAtOne(); p.Play(PlayDirection.Reverse); }); break; case ShowHideExecute.InstantShow: hideProgressors.ForEach(p => p.Stop()); showProgressors.ForEach(p => p.SetProgressAtOne()); showHideProgressors.ForEach(p => p.SetProgressAtOne()); break; case ShowHideExecute.InstantHide: showProgressors.ForEach(p => p.Stop()); hideProgressors.ForEach(p => p.SetProgressAtOne()); showHideProgressors.ForEach(p => p.SetProgressAtZero()); break; case ShowHideExecute.ReverseShow: hideProgressors.ForEach(p => p.Stop()); if (previouslyExecutedCommand == ShowHideExecute.ReverseShow) { showProgressors.ForEach(p => { if (p.reaction.isActive) { p.Reverse(); } else { p.Play(PlayDirection.Forward); } }); showHideProgressors.ForEach(p => { if (p.reaction.isActive && p.reaction.direction == PlayDirection.Reverse) { p.Reverse(); } else { p.Play(PlayDirection.Forward); } }); break; } showProgressors.ForEach(p => { if (p.reaction.isActive) { p.Reverse(); } else { p.Play(PlayDirection.Reverse); } }); showHideProgressors.ForEach(p => { if (p.reaction.isActive && p.reaction.direction == PlayDirection.Forward) { p.Reverse(); } else { p.Play(PlayDirection.Reverse); } }); break; case ShowHideExecute.ReverseHide: showProgressors.ForEach(p => p.Stop()); if (previouslyExecutedCommand == ShowHideExecute.ReverseHide) { hideProgressors.ForEach(p => { if (p.reaction.isActive) { p.Reverse(); } else { p.Play(PlayDirection.Forward); } }); showHideProgressors.ForEach(p => { if (p.reaction.isActive && p.reaction.direction == PlayDirection.Forward) { p.Reverse(); } else { p.Play(PlayDirection.Reverse); } }); break; } hideProgressors.ForEach(p => { if (p.reaction.isActive) { p.Reverse(); } else { p.Play(PlayDirection.Reverse); } }); showHideProgressors.ForEach(p => { if (p.reaction.isActive && p.reaction.direction == PlayDirection.Reverse) { p.Reverse(); } else { p.Play(PlayDirection.Forward); } }); break; default: throw new ArgumentOutOfRangeException(nameof(command), command, null); } previouslyExecutedCommand = command; // ReSharper restore Unity.NoNullPropagation } #region Instant Show/Hide/Toggle /// /// Show in the current frame without animations. /// Triggers visibility states IsShowing and then Visible. /// public virtual void InstantShow() => InstantShow(true); /// /// Show in the current frame without animations. /// Triggers visibility states IsShowing and then Visible. /// /// Should callbacks be triggered or not public virtual void InstantShow(bool triggerCallbacks) { StopDelayedShowCoroutine(); StopDelayedHideCoroutine(); if (isVisible) return; StopIsShowingCoroutine(); StopIsHidingCoroutine(); if (hasCanvas) canvas.enabled = true; //enable the canvas if (hasGraphicRaycaster & DisableGraphicRaycasterWhenHidden) graphicRaycaster.enabled = true; //enable the graphic raycaster if (hasCanvasGroup & HandleCanvasGroupBlockRaycasts) canvasGroup.blocksRaycasts = true; //enable blocks raycasts gameObject.SetActive(true); //set the active state to true (in case it has been disabled when hidden) ExecutedCommand(ShowHideExecute.InstantShow); if (ClearSelectedOnShow) { SetSelected(null); //clear any selected object } if (AutoSelectAfterShow && AutoSelectTarget != null) //check that the auto select option is enabled and that a GameObject has been referenced { SetSelected(AutoSelectTarget); //select the referenced target } SetVisibility(VisibilityState.IsShowing, triggerCallbacks); SetVisibility(VisibilityState.Visible, triggerCallbacks); } /// /// Hide in the current frame without animations. /// Triggers visibility states IsHiding and then Hidden. /// public virtual void InstantHide() => InstantHide(true); /// /// Hide in the current frame without animations. /// Triggers visibility states IsHiding and then Hidden. /// /// Should callbacks be triggered or not public virtual void InstantHide(bool triggerCallbacks) { StopDelayedShowCoroutine(); StopDelayedHideCoroutine(); if (isHidden) return; StopIsShowingCoroutine(); StopIsHidingCoroutine(); ExecutedCommand(ShowHideExecute.InstantHide); if (ClearSelectedOnHide) { SetSelected(null); //clear any selected object } SetVisibility(VisibilityState.IsHiding, triggerCallbacks); SetVisibility(VisibilityState.Hidden, triggerCallbacks); } /// /// Show or hide in the current frame without animations. /// /// Should trigger show or hide public virtual void InstantShowHide(bool show) => InstantShowHide(show, true); /// /// Show or hide in the current frame without animations. /// /// Should trigger show or hide /// Should callbacks be triggered or not public virtual void InstantShowHide(bool show, bool triggerCallbacks) { if (show) { InstantShow(triggerCallbacks); return; } InstantHide(triggerCallbacks); } /// /// Toggles the visibility state. /// If Visible or IsShowing calls InstantHide. /// If Hidden or IsHiding calls InstantShow. /// public virtual void InstantToggle() => InstantToggle(true); /// /// Toggles the visibility state. /// If Visible or IsShowing calls InstantHide. /// If Hidden or IsHiding calls InstantShow. /// /// Should callbacks be triggered or not public virtual void InstantToggle(bool triggerCallbacks) { switch (visibilityState) { case VisibilityState.Visible: case VisibilityState.IsShowing: InstantHide(); break; case VisibilityState.Hidden: case VisibilityState.IsHiding: InstantShow(); break; default: throw new ArgumentOutOfRangeException(); } } #endregion #region Animated Show/Hide/Toggle /// /// Show with animations. /// Triggers visibility states IsShowing when Show starts and then Visible when Show finished. /// public virtual void Show() => Show(true); /// /// Show with animations. /// Triggers visibility states IsShowing when Show starts and then Visible when Show finished. /// /// Should callbacks be triggered or not public virtual void Show(bool triggerCallbacks) { StopDelayedShowCoroutine(); StopDelayedHideCoroutine(); if (isShowing || isVisible) return; gameObject.SetActive(true); //set the active state to true (in case it has been disabled when hidden) if (m_LastFrameVisibilityStateChanged == Time.frameCount) { StartDelayedShowCoroutine(triggerCallbacks); return; } if (ClearSelectedOnShow) { SetSelected(null); //clear any selected object } if (hasCanvas) canvas.enabled = true; //enable the canvas if (hasGraphicRaycaster & DisableGraphicRaycasterWhenHidden) graphicRaycaster.enabled = true; //enable the graphic raycaster if (hasCanvasGroup & HandleCanvasGroupBlockRaycasts) canvasGroup.blocksRaycasts = true; //enable blocks raycasts if (isHiding) { StopIsHidingCoroutine(); ExecutedCommand(ShowHideExecute.ReverseHide); m_CoroutineIsShowing = StartCoroutine(IsShowing(triggerCallbacks)); return; } ExecutedCommand(ShowHideExecute.Show); m_CoroutineIsShowing = StartCoroutine(IsShowing(triggerCallbacks)); } private void StartDelayedShowCoroutine(bool triggerCallbacks) { StopDelayedShowCoroutine(); m_DelayedShowCoroutine = StartCoroutine ( Coroutiner.DelayExecution ( () => { Show(triggerCallbacks); }, 2 //2 frames delay ) ); } private void StopDelayedShowCoroutine() { if (m_DelayedShowCoroutine == null) return; StopCoroutine(m_DelayedShowCoroutine); m_DelayedShowCoroutine = null; } private void StopIsShowingCoroutine() { if (m_CoroutineIsShowing == null) return; StopCoroutine(m_CoroutineIsShowing); m_CoroutineIsShowing = null; } /// /// Internal functionality used by the Show process. /// Triggered by Show /// /// Should callbacks be triggered or not private IEnumerator IsShowing(bool triggerCallbacks) { StopIsHidingCoroutine(); SetVisibility(VisibilityState.IsShowing, triggerCallbacks); yield return new WaitForEndOfFrame(); while (anyAnimationIsActive) yield return null; if (hasProgressors) while (anyProgressorIsActive) yield return null; if (AutoSelectAfterShow && AutoSelectTarget != null) //check that the auto select option is enabled and that a GameObject has been referenced { SetSelected(AutoSelectTarget); //select the referenced target } SetVisibility(VisibilityState.Visible, triggerCallbacks); m_CoroutineIsShowing = null; } /// /// Hide with animations. /// Triggers visibility states IsHiding when Hide starts and then Hidden when Hide finished. /// public virtual void Hide() => Hide(true); /// /// Hide with animations. /// Triggers visibility states IsHiding when Hide starts and then Hidden when Hide finished. /// /// Should callbacks be triggered or not public virtual void Hide(bool triggerCallbacks) { if (!isActiveAndEnabled) return; StopDelayedShowCoroutine(); StopDelayedHideCoroutine(); if (isHiding || isHidden) return; if (m_LastFrameVisibilityStateChanged == Time.frameCount) { StartDelayedHideCoroutine(triggerCallbacks); return; } if (ClearSelectedOnHide) { SetSelected(null); //clear any selected object } if (isShowing) { StopIsShowingCoroutine(); ExecutedCommand(ShowHideExecute.ReverseShow); m_CoroutineIsHiding = StartCoroutine(IsHiding(triggerCallbacks)); return; } ExecutedCommand(ShowHideExecute.Hide); m_CoroutineIsHiding = StartCoroutine(IsHiding(triggerCallbacks)); } private void StartDelayedHideCoroutine(bool triggerCallbacks) { StopDelayedHideCoroutine(); m_DelayedHideCoroutine = StartCoroutine ( Coroutiner.DelayExecution ( () => { m_DelayedHideCoroutine = null; Hide(triggerCallbacks); }, 2 //2 frames delay ) ); } private void StopDelayedHideCoroutine() { if (m_DelayedHideCoroutine == null) return; StopCoroutine(m_DelayedHideCoroutine); m_DelayedHideCoroutine = null; } private void StopIsHidingCoroutine() { StopDisableGameObject(); if (m_CoroutineIsHiding == null) return; StopCoroutine(m_CoroutineIsHiding); m_CoroutineIsHiding = null; } /// /// Internal functionality used by the Hide process. /// Triggered by Hide /// /// Should callbacks be triggered or not private IEnumerator IsHiding(bool triggerCallbacks) { StopDisableGameObject(); StopIsShowingCoroutine(); SetVisibility(VisibilityState.IsHiding, triggerCallbacks); yield return new WaitForEndOfFrame(); while (anyAnimationIsActive) yield return null; if (hasProgressors) while (anyProgressorIsActive) yield return null; SetVisibility(VisibilityState.Hidden, triggerCallbacks); m_CoroutineIsHiding = null; } /// /// Show or Hide with animations. /// /// Show or Hide public virtual void ShowHide(bool show) => ShowHide(show, true); /// /// Show or Hide with animations. /// /// Show or Hide /// Should callbacks be triggered or not public virtual void ShowHide(bool show, bool triggerCallbacks) { if (show) { Show(triggerCallbacks); return; } Hide(triggerCallbacks); } /// /// Toggle the visibility state. /// If Visible or IsShowing calls Hide. /// If Hidden or IsHiding calls Show. /// public virtual void Toggle() => Toggle(true); /// /// Toggle the visibility state. /// If Visible or IsShowing calls Hide. /// If Hidden or IsHiding calls Show. /// /// Should callbacks be triggered or not public virtual void Toggle(bool triggerCallbacks) { switch (visibilityState) { case VisibilityState.Visible: case VisibilityState.IsShowing: Hide(triggerCallbacks); break; case VisibilityState.Hidden: case VisibilityState.IsHiding: Show(triggerCallbacks); break; default: throw new ArgumentOutOfRangeException(); } } #endregion /// Set the container visibility /// New visibility state /// Should callbacks be triggered or not internal void SetVisibility(VisibilityState state, bool triggerCallbacks) { m_LastFrameVisibilityStateChanged = Time.frameCount; VisibilityState = state; if (triggerCallbacks) OnVisibilityChangedCallback?.Invoke(VisibilityState); switch (state) { case VisibilityState.Visible: ExecuteOnVisible(triggerCallbacks); break; case VisibilityState.Hidden: ExecuteOnHidden(triggerCallbacks); break; case VisibilityState.IsShowing: ExecuteOnShow(triggerCallbacks); break; case VisibilityState.IsHiding: ExecuteOnHide(triggerCallbacks); break; default: throw new ArgumentOutOfRangeException(nameof(state), state, null); } } /// /// Execute internal operations when the Show animation /// is in the process (transition) of becoming visible. /// Triggered at the start of the Show animation. /// /// Should callbacks be triggered or not private void ExecuteOnShow(bool triggerCallbacks) { if (triggerCallbacks) { OnShowCallback.Execute(); } } /// /// Execute internal operations when the Hide animation /// is in the process (transition) of becoming hidden. /// Triggered at the start of the Hide animation. /// /// Should callbacks be triggered or not private void ExecuteOnHide(bool triggerCallbacks) { if (triggerCallbacks) { OnHideCallback.Execute(); } if (hasGraphicRaycaster & DisableGraphicRaycasterWhenHidden) graphicRaycaster.enabled = false; //disable graphic raycaster when hidden if (hasCanvasGroup & HandleCanvasGroupBlockRaycasts) canvasGroup.blocksRaycasts = false; //disable blocks raycasts when hidden StopAutoHide(); } /// /// Execute internal operations when the Show animation /// finished and the container Is Visible. /// Triggered at the end of the Show animation. /// /// Should callbacks be triggered or not private void ExecuteOnVisible(bool triggerCallbacks) { if (triggerCallbacks) { OnVisibleCallback.Execute(); } StartAutoHide(); } /// /// Execute internal operations when the Hide animation /// finished and the container Is Hidden. /// Triggered at the end of the Hide animation. /// /// Should callbacks be triggered or not private void ExecuteOnHidden(bool triggerCallbacks) { if (triggerCallbacks) { OnHiddenCallback.Execute(); } if (hasCanvas & DisableCanvasWhenHidden) canvas.enabled = false; //disable canvas when hidden if (hasGraphicRaycaster & DisableGraphicRaycasterWhenHidden) graphicRaycaster.enabled = false; //disable graphic raycaster when hidden if (hasCanvasGroup & HandleCanvasGroupBlockRaycasts) canvasGroup.blocksRaycasts = false; //disable blocks raycasts when hidden StartDisableGameObject(); } private void StartDisableGameObject() { StopDisableGameObject(); m_DisableGameObjectWithDelayCoroutine = StartCoroutine(DisableGameObjectWithDelay()); } private void StopDisableGameObject() { if (m_DisableGameObjectWithDelayCoroutine == null) return; StopCoroutine(m_DisableGameObjectWithDelayCoroutine); m_DisableGameObjectWithDelayCoroutine = null; } private IEnumerator DisableGameObjectWithDelay() { //we need to wait for 3 frames to make sure all the connected animators have had enough time to initialize (it takes 2 frames for a position animator to get its start position from a layout group (THANKS UNITY!!!) FML) yield return null; //wait 1 frame (1 for the money) yield return null; //wait 1 frame (2 for the show) yield return null; //wait 1 frame (3 to get ready) // ...and 4 to f@#king go! gameObject.SetActive(!DisableGameObjectWhenHidden); //set the active state to false, if the option is enabled } private void StartAutoHide() { StopAutoHide(); if (!AutoHideAfterShow) return; m_AutoHideCoroutine = StartCoroutine(AutoHideEnumerator()); } private void StopAutoHide() { if (m_AutoHideCoroutine == null) return; StopCoroutine(m_AutoHideCoroutine); m_AutoHideCoroutine = null; } private IEnumerator AutoHideEnumerator() { yield return new WaitForSecondsRealtime(AutoHideAfterShowDelay); Hide(); m_AutoHideCoroutine = null; } protected virtual void RunBehaviour(ContainerBehaviour behaviour) { switch (behaviour) { case ContainerBehaviour.Disabled: //ignored return; case ContainerBehaviour.InstantHide: VisibilityState = VisibilityState.Visible; InstantHide(); return; case ContainerBehaviour.InstantShow: VisibilityState = VisibilityState.Hidden; InstantShow(); return; case ContainerBehaviour.Hide: VisibilityState = VisibilityState.Visible; Hide(); return; case ContainerBehaviour.Show: InstantHide(false); StartCoroutine(Coroutiner.DelayExecution(Show, 2)); return; default: throw new ArgumentOutOfRangeException(nameof(behaviour), behaviour, null); } } private float CalculateTotalShowDuration() { float duration = CalculateTotalDurationForReactions(showReactions); float maxDelay = 0; float maxDuration = 0; showProgressors.RemoveNulls(); foreach (FloatReaction r in showProgressors.Select(p => p.reaction)) { maxDelay = Mathf.Max(maxDelay, r.settings.useRandomStartDelay ? r.settings.randomStartDelay.max : r.settings.startDelay); maxDuration = Mathf.Max(maxDuration, r.settings.useRandomDuration ? r.settings.randomDuration.max : r.settings.duration); } showHideProgressors.RemoveNulls(); foreach (FloatReaction r in showHideProgressors.Select(p => p.reaction)) { maxDelay = Mathf.Max(maxDelay, r.settings.useRandomStartDelay ? r.settings.randomStartDelay.max : r.settings.startDelay); maxDuration = Mathf.Max(maxDuration, r.settings.useRandomDuration ? r.settings.randomDuration.max : r.settings.duration); } return Mathf.Max(duration, maxDelay + maxDuration); } private float CalculateTotalHideDuration() { float duration = CalculateTotalDurationForReactions(hideReactions); float maxDelay = 0; float maxDuration = 0; hideProgressors.RemoveNulls(); foreach (FloatReaction r in hideProgressors.Select(p => p.reaction)) { maxDelay = Mathf.Max(maxDelay, r.settings.useRandomStartDelay ? r.settings.randomStartDelay.max : r.settings.startDelay); maxDuration = Mathf.Max(maxDuration, r.settings.useRandomDuration ? r.settings.randomDuration.max : r.settings.duration); } showHideProgressors.RemoveNulls(); foreach (FloatReaction r in showHideProgressors.Select(p => p.reaction)) { //don't calculate start delay as this progressor plays in reverse on hide // maxDelay = Mathf.Max(maxDelay, r.settings.useRandomStartDelay ? r.settings.randomStartDelay.max : r.settings.startDelay); maxDuration = Mathf.Max(maxDuration, r.settings.useRandomDuration ? r.settings.randomDuration.max : r.settings.duration); } return Mathf.Max(duration, maxDelay + maxDuration); } private static float CalculateTotalDurationForReactions(IEnumerable reactions, params Reaction[] others) { if (reactions == null) return 0f; float maxDelay = 0; float maxDuration = 0; foreach (Reaction r in reactions) { if (r == null) continue; maxDelay = Mathf.Max(maxDelay, r.settings.useRandomStartDelay ? r.settings.randomStartDelay.max : r.settings.startDelay); maxDuration = Mathf.Max(maxDuration, r.settings.useRandomDuration ? r.settings.randomDuration.max : r.settings.duration); } if (others == null) return maxDelay + maxDuration; foreach (Reaction r in others) { if (r == null) continue; maxDelay = Mathf.Max(maxDelay, r.settings.useRandomStartDelay ? r.settings.randomStartDelay.max : r.settings.startDelay); maxDuration = Mathf.Max(maxDuration, r.settings.useRandomDuration ? r.settings.randomDuration.max : r.settings.duration); } return maxDelay + maxDuration; } } }