// 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 System.Linq; using Doozy.Runtime.Common.Attributes; using Doozy.Runtime.Common.Utils; using Doozy.Runtime.Global; using Doozy.Runtime.Signals; using Doozy.Runtime.UIManager.Containers.Internal; using Doozy.Runtime.UIManager.Orientation; using Doozy.Runtime.UIManager.ScriptableObjects; using UnityEngine; using UnityEngine.UI; namespace Doozy.Runtime.UIManager.Containers { /// /// Container with show and hide capabilities with a category/name id identifier /// [RequireComponent(typeof(Canvas))] [RequireComponent(typeof(GraphicRaycaster))] [RequireComponent(typeof(RectTransform))] [AddComponentMenu("Doozy/UI/Containers/UIView")] public partial class UIView : UIContainerComponent { #if UNITY_EDITOR [UnityEditor.MenuItem("GameObject/Doozy/UI/Containers/UIView", false, 8)] private static void CreateComponent(UnityEditor.MenuCommand menuCommand) { GameObjectUtils.AddToScene("UIView", false, true); } #endif [ClearOnReload] private static SignalStream s_stream; /// Signal stream for this component type public static SignalStream stream => s_stream ??= SignalsService.GetStream(k_StreamCategory, nameof(UIView)); /// Get all the visible views (returns all views that are either in the isVisible or isShowing state) public static IEnumerable visibleViews => database.Where(view => view.isVisible || view.isShowing); /// Get all the hidden views (returns all views that are either in the isHidden or isHiding state) public static IEnumerable hiddenViews => database.Where(view => view.isHidden || view.isHiding); /// UIView signal receiver private SignalReceiver receiver { get; set; } /// Category Name Id public UIViewId Id; [SerializeField] private TargetOrientation TargetOrientation = TargetOrientation.Any; /// Target orientation for this view public TargetOrientation targetOrientation { get => TargetOrientation; set => TargetOrientation = value; } /// Returns TRUE if Orientation Detection is enabled private static bool useOrientationDetection => UIManagerSettings.instance.UseOrientationDetection; /// Current orientation of the device found by the OrientationDetector private static DetectedOrientation currentDeviceOrientation => OrientationDetector.instance.currentOrientation; public UIView() { Id = new UIViewId(); } protected override void Awake() { base.Awake(); receiver = new SignalReceiver().SetOnSignalCallback(ProcessSignal); stream.ConnectReceiver(receiver); ConnectToOrientationDetector(); } protected override void OnDestroy() { base.OnDestroy(); stream.DisconnectReceiver(receiver); DisconnectFromOrientationDetector(); } private void ProcessSignal(Signal signal) { if (!signal.hasValue) return; if (!(signal.valueAsObject is UIViewSignalData data)) return; if (multiplayerMode & hasMultiplayerInfo && data.playerIndex != playerIndex) return; if (data.globalCommand) //global command (bypass category and name checks) -> execute { Execute(data.execute); return; } if (!data.viewCategory.Equals(Id.Category)) //check category return; //category match found - CONTINUE if (data.categoryCommand) //category command (bypass name check) -> execute { Execute(data.execute); return; } if (!data.viewName.Equals(Id.Name)) //check name return; //name match found - CONTINUE Execute(data.execute); } /// Check if show can be executed when the orientation detection is enabled private bool canShow { get { if (!useOrientationDetection) return true; if (currentDeviceOrientation == DetectedOrientation.Unknown) return false; switch (targetOrientation) { case TargetOrientation.Any: return true; case TargetOrientation.Portrait: return currentDeviceOrientation == DetectedOrientation.Portrait; case TargetOrientation.Landscape: return currentDeviceOrientation == DetectedOrientation.Landscape; default: throw new ArgumentOutOfRangeException(); } } } private void Execute(ShowHideExecute execute) { if (useOrientationDetection && currentDeviceOrientation == DetectedOrientation.Unknown) //orientation detection is enabled and the current device orientation is unknown { StartCoroutine(Coroutiner.DelayExecutionToTheNextFrame(() => Execute(execute))); //wait for the next frame and try again return; //exit } // Debug.Log($"[{Time.frameCount}] [DO:{currentDeviceOrientation}] [TO:{targetOrientation}] Can Show: {canShow} -> {execute}"); switch (execute) { case ShowHideExecute.Show: if (canShow) { Show(); } else { InstantHide(false); } break; case ShowHideExecute.Hide: Hide(); break; case ShowHideExecute.InstantShow: if (canShow) { InstantShow(); } else { InstantHide(false); } break; case ShowHideExecute.InstantHide: InstantHide(); break; case ShowHideExecute.ReverseShow: case ShowHideExecute.ReverseHide: //ignored as these states are relevant only for animators break; default: throw new ArgumentOutOfRangeException(); } } /// /// Connects to the OrientationDetector and listens for orientation changes, /// if UseOrientationDetection is enabled in the UIManagerSettings /// private void ConnectToOrientationDetector() { if (useOrientationDetection && OrientationDetector.instance != null) OrientationDetector.instance.OnOrientationChanged.AddListener(OnOrientationChanged); } /// Disconnects from the OrientationDetector and stops listening for orientation changes private void DisconnectFromOrientationDetector() { if (useOrientationDetection && OrientationDetector.instance != null) OrientationDetector.instance.OnOrientationChanged.RemoveListener(OnOrientationChanged); } /// React to the OrientationDetector.OnOrientationChanged event /// New orientation private void OnOrientationChanged(DetectedOrientation orientation) { switch (orientation) { case DetectedOrientation.Unknown: //do nothing return; case DetectedOrientation.Portrait: if (targetOrientation == TargetOrientation.Landscape) { if (isVisible | isShowing) { InstantHide(); Coroutiner.ExecuteAtEndOfFrame(() => Show(Id.Category, Id.Name)); } } break; case DetectedOrientation.Landscape: if (targetOrientation == TargetOrientation.Portrait) { if (isVisible | isShowing) { InstantHide(); Coroutiner.ExecuteAtEndOfFrame(() => Show(Id.Category, Id.Name)); } } return; default: throw new ArgumentOutOfRangeException(nameof(orientation), orientation, null); } } protected override void RunBehaviour(ContainerBehaviour behaviour) { if (!useOrientationDetection) //if orientation detection is disabled, run the behaviour as usual { base.RunBehaviour(behaviour); return; } if (currentDeviceOrientation != DetectedOrientation.Unknown) //if the orientation is known, run the behaviour as usual { switch (behaviour) { case ContainerBehaviour.Disabled: { //ignored return; } case ContainerBehaviour.InstantHide: { VisibilityState = VisibilityState.Visible; InstantHide(); return; } case ContainerBehaviour.InstantShow: { if (canShow) { VisibilityState = VisibilityState.Hidden; InstantShow(); } else { InstantHide(false); } return; } case ContainerBehaviour.Hide: { VisibilityState = VisibilityState.Visible; Hide(); return; } case ContainerBehaviour.Show: { InstantHide(false); if (canShow) { StartCoroutine(Coroutiner.DelayExecution(Show, 2)); } return; } default: throw new ArgumentOutOfRangeException(nameof(behaviour), behaviour, null); } } //if the orientation is unknown, wait for it to be known and then run the behaviour StartCoroutine(Coroutiner.DelayExecutionToTheNextFrame(() => RunBehaviour(behaviour))); } #region Static Methods /// Get all the registered views with the given category and name /// UIView category /// UIView name (from the given category) public static IEnumerable GetViews(string category, string name) => database.Where(view => view.Id.Category.Equals(category)).Where(view => view.Id.Name.Equals(name)); /// Get all the registered views with the given category /// UIView category public static IEnumerable GetAllViewsInCategory(string category) => database.Where(view => view.Id.Category.Equals(category)); /// Show/Hide all the views with the given category and name /// UIView category /// UIView name (from the given category) /// What to execute /// Player index internal static void Toggle(string category, string name, ShowHideExecute execute, int playerIndex) => stream.SendSignal(new UIViewSignalData(category, name, execute, playerIndex)); #region Show /// Show all the views with the given category and name /// UIView category /// UIView name (from the given category) /// Should the transition be instant or animated /// Player index public static void Show(string category, string name, bool instant, int playerIndex) => Toggle(category, name, instant ? ShowHideExecute.InstantShow : ShowHideExecute.Show, playerIndex); /// Show all the views with the given category and name /// UIView category /// UIView name (from the given category) /// Should the transition be instant or animated public static void Show(string category, string name, bool instant = false) => Show(category, name, instant, inputSettings.defaultPlayerIndex); /// Show all the views in the given category (regardless of the view name) /// UIView category /// Should the transition be instant or animated /// Player index public static void ShowCategory(string category, bool instant, int playerIndex) => Show(category, string.Empty, instant, playerIndex); /// Show all the views in the given category (regardless of the view name) /// UIView category /// Should the transition be instant or animated public static void ShowCategory(string category, bool instant = false) => ShowCategory(category, instant, inputSettings.defaultPlayerIndex); #endregion #region Hide /// Hide all the views with the given category and name /// UIView category /// UIView name (from the given category) /// Should the transition be instant or animated /// Player index public static void Hide(string category, string name, bool instant, int playerIndex) => Toggle(category, name, instant ? ShowHideExecute.InstantHide : ShowHideExecute.Hide, playerIndex); /// Hide all the views with the given category and name /// UIView category /// UIView name (from the given category) /// Should the transition be instant or animated public static void Hide(string category, string name, bool instant = false) => Hide(category, name, instant, inputSettings.defaultPlayerIndex); /// Hide all views in the given category (regardless of the view name) /// UIView category /// Should the transition be instant or animated /// Player index public static void HideCategory(string category, bool instant, int playerIndex) => Hide(category, string.Empty, instant, playerIndex); /// Hide all views in the given category (regardless of the view name) /// UIView category /// Should the transition be instant or animated public static void HideCategory(string category, bool instant = false) => HideCategory(category, instant, inputSettings.defaultPlayerIndex); /// Hide all views regardless of their state /// Should the transition be instant or animated /// Player index public static void HideAllViews(bool instant, int playerIndex) => stream.SendSignal(new UIViewSignalData(string.Empty, string.Empty, instant ? ShowHideExecute.InstantHide : ShowHideExecute.Hide, playerIndex)); /// Hide all views regardless of their state /// Should the transition be instant or animated public static void HideAllViews(bool instant = false) => stream.SendSignal(new UIViewSignalData(string.Empty, string.Empty, instant ? ShowHideExecute.InstantHide : ShowHideExecute.Hide, inputSettings.defaultPlayerIndex)); #endregion #endregion } }