From 150db89fbe621fb9b17bea9cc7f4692d8e9ce402 Mon Sep 17 00:00:00 2001 From: Vatsal Ambastha Date: Fri, 30 Apr 2021 22:40:32 +0530 Subject: [PATCH] feat: Several - Window now has virtual methods corresponding to each event - StatefulView now updates view on start using an explicit flag - PictureEditor cleaned up - Texture2DDownloader.Download method downloads using UniTask - Initializer has more features - ViewList.SubscscribeToChildren is now ViewList.InvokeAllChildViews - StatefulView abstract method on state set is now OnStateSet - StatefulView event on state set is StateSet Break: Renamed properties, fields and methods. Some kept intact with Obsolete attribute --- Assets/Adrenak.UGX/Editor/PictureEditor.cs | 14 +- Assets/Adrenak.UGX/Editor/WindowEditor.cs | 2 - Assets/Adrenak.UGX/README.md | 4 +- .../Components/Picture/PictureMemoryCache.cs | 6 +- .../Runtime/Navigation/DefaultUGXNavigator.cs | 22 +- .../Runtime/Navigation/Navigator.cs | 66 ++-- .../Adrenak.UGX/Runtime/Popup/AlertPopup.cs | 4 +- Assets/Adrenak.UGX/Runtime/Popup/AskPopup.cs | 4 +- .../Runtime/Popup/AskWithFlagPopup.cs | 4 +- .../Runtime/Popup/NotificationPopup.cs | 4 +- Assets/Adrenak.UGX/Runtime/Popup/Popup.cs | 22 +- Assets/Adrenak.UGX/Runtime/View/IconView.cs | 59 ---- .../Adrenak.UGX/Runtime/View/IconView.cs.meta | 11 - Assets/Adrenak.UGX/Runtime/View/State.cs | 8 - .../Adrenak.UGX/Runtime/View/StatefulView.cs | 68 +++- Assets/Adrenak.UGX/Runtime/View/View.cs | 18 +- Assets/Adrenak.UGX/Runtime/View/ViewList.cs | 294 ++++++++++++------ Assets/Adrenak.UGX/Runtime/View/ViewState.cs | 14 + .../View/{State.cs.meta => ViewState.cs.meta} | 0 Assets/Adrenak.UGX/Runtime/Window/Window.cs | 86 ++++- .../Runtime/Window/WindowFullscreenAddon.cs | 3 + .../Window/WindowScreenOrientationAddon.cs | 3 + .../Runtime/Window/WindowStatus.cs | 2 +- .../Runtime/_Misc/Texture2DDownloader.cs | 43 ++- .../Adrenak.UGX/Runtime/_Misc/UGXBehaviour.cs | 33 +- .../Runtime/_Misc/UGXExtensions.cs | 96 +++++- .../Runtime/_Misc/UGXInitializer.cs | 34 ++ 27 files changed, 636 insertions(+), 288 deletions(-) delete mode 100644 Assets/Adrenak.UGX/Runtime/View/IconView.cs delete mode 100644 Assets/Adrenak.UGX/Runtime/View/IconView.cs.meta delete mode 100644 Assets/Adrenak.UGX/Runtime/View/State.cs create mode 100644 Assets/Adrenak.UGX/Runtime/View/ViewState.cs rename Assets/Adrenak.UGX/Runtime/View/{State.cs.meta => ViewState.cs.meta} (100%) diff --git a/Assets/Adrenak.UGX/Editor/PictureEditor.cs b/Assets/Adrenak.UGX/Editor/PictureEditor.cs index 4809d6f..63c9d69 100644 --- a/Assets/Adrenak.UGX/Editor/PictureEditor.cs +++ b/Assets/Adrenak.UGX/Editor/PictureEditor.cs @@ -1,5 +1,6 @@ using UnityEditor; using UnityEditor.UI; + using UnityEngine; namespace Adrenak.UGX { @@ -17,11 +18,11 @@ public override void OnInspectorGUI() { EditorGUI.EndDisabledGroup(); showEvents = EditorGUILayout.Foldout(showEvents, new GUIContent("Events")); - if (showEvents) { + if (showEvents) { EditorGUILayout.PropertyField(serializedObject.FindProperty("onLoadStart"), new GUIContent("On Load Start")); EditorGUILayout.PropertyField(serializedObject.FindProperty("onLoadSuccess"), new GUIContent("On Load Success")); - EditorGUILayout.PropertyField(serializedObject.FindProperty("onLoadFailure"), new GUIContent("On Load Failure")); - } + EditorGUILayout.PropertyField(serializedObject.FindProperty("onLoadFailure"), new GUIContent("On Load Failure")); + } image.refreshOnStart = EditorGUILayout.Toggle("Refresh On Start", image.refreshOnStart); image.updateWhenOffScreen = EditorGUILayout.Toggle("Update When Off Screen", image.updateWhenOffScreen); @@ -31,15 +32,12 @@ public override void OnInspectorGUI() { image.compression = (Texture2DCompression)EditorGUILayout.EnumPopup("Texture Compression", image.compression); EditorGUILayout.Space(); - image.source = (Picture.Source)EditorGUILayout.EnumPopup("Source Type", image.source); - - if(image.source != Picture.Source.Asset) - image.path = EditorGUILayout.TextField("Source Path", image.path); + image.path = EditorGUILayout.TextField("Source Path", image.path); serializedObject.ApplyModifiedProperties(); if (GUILayout.Button("Refresh Picture")) - image.Refresh(); + image.Refresh(); } } } \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Editor/WindowEditor.cs b/Assets/Adrenak.UGX/Editor/WindowEditor.cs index 8e15dba..749dfb7 100644 --- a/Assets/Adrenak.UGX/Editor/WindowEditor.cs +++ b/Assets/Adrenak.UGX/Editor/WindowEditor.cs @@ -17,8 +17,6 @@ public override void OnInspectorGUI() { EditorGUILayout.EnumPopup("Status", window.Status); EditorGUI.EndDisabledGroup(); - window.autoPopOnBack = EditorGUILayout.Toggle("Auto-Pop on back", window.autoPopOnBack); - showEvents = EditorGUILayout.Foldout(showEvents, new GUIContent("Events")); if (showEvents) { EditorGUILayout.PropertyField(serializedObject.FindProperty("onWindowStartOpening"), new GUIContent("On Window Start Opening")); diff --git a/Assets/Adrenak.UGX/README.md b/Assets/Adrenak.UGX/README.md index 54a435a..b83941d 100644 --- a/Assets/Adrenak.UGX/README.md +++ b/Assets/Adrenak.UGX/README.md @@ -1,6 +1,8 @@ ## UGX -Unity GUI Extended +UGX (**U**nity u**G**UI E**x**tended) is a library over uGUI for creating UI in Unity. + +UGX provides high-level features such as windows, state management, animations, navigation over the inbuilt uGUI components. ### Docs coming soon In the meantime, checkout the [samples](https://www.github.com/adrenak/ugx-samples) diff --git a/Assets/Adrenak.UGX/Runtime/Components/Picture/PictureMemoryCache.cs b/Assets/Adrenak.UGX/Runtime/Components/Picture/PictureMemoryCache.cs index 4eed84c..05b1a1f 100644 --- a/Assets/Adrenak.UGX/Runtime/Components/Picture/PictureMemoryCache.cs +++ b/Assets/Adrenak.UGX/Runtime/Components/Picture/PictureMemoryCache.cs @@ -73,7 +73,7 @@ public override void Get(string location, Texture2DCompression compression, Pict } var request = new Request(onSuccess, onFailure); - requests.EnsureContains(key, new List() { }); + requests.EnsureKey(key, new List() { }); requests[key].Add(request); if (requests[key].Count > 1) return; @@ -85,8 +85,8 @@ public override void Get(string location, Texture2DCompression compression, Pict var resultSprite = result.ToSprite(); freed.EnsureDoesntContain(key); - resources.EnsureContains(key, resultSprite); - instances.EnsureContains(key, new List()); + resources.EnsureKey(key, resultSprite); + instances.EnsureKey(key, new List()); instances[key].Add(instance); foreach (var req in requests[key]) diff --git a/Assets/Adrenak.UGX/Runtime/Navigation/DefaultUGXNavigator.cs b/Assets/Adrenak.UGX/Runtime/Navigation/DefaultUGXNavigator.cs index ebc3f6a..7d5f65c 100644 --- a/Assets/Adrenak.UGX/Runtime/Navigation/DefaultUGXNavigator.cs +++ b/Assets/Adrenak.UGX/Runtime/Navigation/DefaultUGXNavigator.cs @@ -11,7 +11,7 @@ protected override void PushImpl(Window window) { // First push if (History.Count == 0) { History.Add(window); - SetAsActive(window); + SetAsCurrent(window); return; } @@ -22,28 +22,30 @@ protected override void PushImpl(Window window) { // Alternate repeat push if (History.Count > 1 && History.FromLast(1).gameObject == window.gameObject) { History.RemoveAt(History.Count - 1); - SetAsActive(History.Last()); + SetAsCurrent(History.Last()); return; } // All other cases History.Add(window); - SetAsActive(window); + SetAsCurrent(window); - onPush.Invoke(); + onPush.Invoke(window); } protected override void PopImpl() { if (History.Count > 1) { - History.RemoveAt(History.Count - 1); - SetAsActive(History.Last()); - onPop.Invoke(); + var last = History.Last(); + History.RemoveLast(); + SetAsCurrent(History.Last()); + onPop.Invoke(last); } else if(History.Count == 1){ History.Last().CloseWindow(); - History.RemoveAt(0); - active = null; - onPop.Invoke(); + var first = History[0]; + History.Remove(first); + current = null; + onPop.Invoke(first); } } } diff --git a/Assets/Adrenak.UGX/Runtime/Navigation/Navigator.cs b/Assets/Adrenak.UGX/Runtime/Navigation/Navigator.cs index 865625d..b2b9808 100644 --- a/Assets/Adrenak.UGX/Runtime/Navigation/Navigator.cs +++ b/Assets/Adrenak.UGX/Runtime/Navigation/Navigator.cs @@ -6,6 +6,9 @@ namespace Adrenak.UGX { public abstract class Navigator : MonoBehaviour { + [System.Serializable] + public class WindowUnityEvent : UnityEvent { } + static Dictionary map = new Dictionary(); public static Navigator Get(string browserID = null) { if (map.ContainsKey(browserID)) @@ -13,19 +16,19 @@ public static Navigator Get(string browserID = null) { return null; } + public WindowUnityEvent onPush = new WindowUnityEvent(); + public WindowUnityEvent onPop = new WindowUnityEvent(); + #pragma warning disable 0649 [SerializeField] string browserID; [SerializeField] bool canPopAll; - [ReadOnly] [SerializeField] protected Window active = null; - public Window Current => active; + [ReadOnly] [SerializeField] protected Window current = null; + public Window Current => current; [ReadOnly] [ReorderableList] [SerializeField] protected List history = new List(); public List History => history; - public UnityEvent onPush; - public UnityEvent onPop; - [SerializeField] protected bool useInitialWindow; [ShowIf("useInitialWindow")] [SerializeField] protected Window initialWindow; #pragma warning restore 0649 @@ -52,41 +55,66 @@ void UnregisterInstance() { void CheckBackPress() { if (!Input.GetKeyUp(KeyCode.Escape)) return; - if (!active.autoPopOnBack) return; Pop(); } - protected void SetAsActive(Window window) { - window.OpenWindow(); - if (active != null) - active.CloseWindow(); - active = window; - } - + // ================================================ + // API / PUBLIC + // ================================================ + /// + /// Push a Window to the navigation and open it. + /// public void Push(Window window) { PushImpl(window); } + /// + /// Pops a Window from the navigation and closes it + /// public void Pop() { if (History.Count == 1 && !canPopAll) return; PopImpl(); } + /// + /// Pops all Windows + /// public void Clear() { while (History.Count > 0) PopImpl(); } - protected abstract void PushImpl(Window window); - - protected abstract void PopImpl(); - + /// + /// Pops a Window if it's active, Push it if it's not. + /// public void Toggle(Window window) { if (History.Count > 0 && History.Last() == window) Pop(); else - Push(window); - } + Push(window); + } + + // ================================================ + // CONTRACT + // ================================================ + /// + /// Actual Push logic to be implemented by subclass + /// + /// + protected abstract void PushImpl(Window window); + + /// + /// Actual Pop logic to be implemented by subclass + /// + protected abstract void PopImpl(); + + // ================================================ + protected void SetAsCurrent(Window window) { + window.OpenWindow(); + if (current != null) + current.CloseWindow(); + current = window; + } } } diff --git a/Assets/Adrenak.UGX/Runtime/Popup/AlertPopup.cs b/Assets/Adrenak.UGX/Runtime/Popup/AlertPopup.cs index 803cd05..161a542 100644 --- a/Assets/Adrenak.UGX/Runtime/Popup/AlertPopup.cs +++ b/Assets/Adrenak.UGX/Runtime/Popup/AlertPopup.cs @@ -26,7 +26,7 @@ async protected override UniTask GetResponse() { return new PopupResponse(); } - protected override void HandlePopupStateSet() { + protected override void OnStateSet() { headerDisplay.text = State.header; bodyDisplay.text = State.description; ackDisplay.text = State.ack; @@ -35,5 +35,7 @@ protected override void HandlePopupStateSet() { public void Acknowledge() { OnAcknowledge?.Invoke(); } + + protected override void OnStart() { } } } diff --git a/Assets/Adrenak.UGX/Runtime/Popup/AskPopup.cs b/Assets/Adrenak.UGX/Runtime/Popup/AskPopup.cs index beb1bd7..2c7828c 100644 --- a/Assets/Adrenak.UGX/Runtime/Popup/AskPopup.cs +++ b/Assets/Adrenak.UGX/Runtime/Popup/AskPopup.cs @@ -35,7 +35,7 @@ async protected override UniTask GetResponse() { return new AskPopupResponse { positive = response.Value }; } - protected override void HandlePopupStateSet() { + protected override void OnStateSet() { headerDisplay.text = State.header; bodyDisplay.text = State.body; positiveDisplay.text = State.positive; @@ -47,5 +47,7 @@ protected override void HandlePopupStateSet() { Action OnDeny; public void Deny() => OnDeny?.Invoke(); + + protected override void OnStart() { } } } diff --git a/Assets/Adrenak.UGX/Runtime/Popup/AskWithFlagPopup.cs b/Assets/Adrenak.UGX/Runtime/Popup/AskWithFlagPopup.cs index e9c3ede..06db4af 100644 --- a/Assets/Adrenak.UGX/Runtime/Popup/AskWithFlagPopup.cs +++ b/Assets/Adrenak.UGX/Runtime/Popup/AskWithFlagPopup.cs @@ -39,7 +39,7 @@ async protected override UniTask GetResponse() { }; } - protected override void HandlePopupStateSet() { + protected override void OnStateSet() { headerDisplay.text = State.header; bodyDisplay.text = State.body; positiveDisplay.text = State.positive; @@ -52,5 +52,7 @@ protected override void HandlePopupStateSet() { Action OnDeny; public void Deny() => OnDeny?.Invoke(); + + protected override void OnStart() { } } } diff --git a/Assets/Adrenak.UGX/Runtime/Popup/NotificationPopup.cs b/Assets/Adrenak.UGX/Runtime/Popup/NotificationPopup.cs index 1086378..8b4dbce 100644 --- a/Assets/Adrenak.UGX/Runtime/Popup/NotificationPopup.cs +++ b/Assets/Adrenak.UGX/Runtime/Popup/NotificationPopup.cs @@ -19,9 +19,11 @@ async protected override UniTask GetResponse() { return new PopupResponse(); } - protected override void HandlePopupStateSet() { + protected override void OnStateSet() { title.text = State.title; description.text = State.description; } + + protected override void OnStart() { } } } \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Runtime/Popup/Popup.cs b/Assets/Adrenak.UGX/Runtime/Popup/Popup.cs index f11c8e3..f1a747e 100644 --- a/Assets/Adrenak.UGX/Runtime/Popup/Popup.cs +++ b/Assets/Adrenak.UGX/Runtime/Popup/Popup.cs @@ -3,12 +3,23 @@ using Cysharp.Threading.Tasks; namespace Adrenak.UGX { + /// + /// Base class for states of popup implementations + /// [SerializeField] public class PopupState : ViewState { } + /// + /// Base class for response of popup implementation + /// [Serializable] public class PopupResponse { } + /// + /// Base class for popups. Inherit from this class to implement different kinds of popups + /// + /// + /// [RequireComponent(typeof(Window))] public abstract class Popup : StatefulView where TState : PopupState where Response : PopupResponse { /// @@ -44,7 +55,7 @@ async public void SetStateAndDisplay(TState state, Action responseCall responseCallback?.Invoke(await SetStateAndDisplay(state)); /// - /// Displays the popup with a given state modifier and returns the response as a task + /// Displays the popup with a given state modifier action and returns the response as a task /// async public UniTask ModifyStateAndDisplay(Action stateModifier) { stateModifier?.Invoke(State); @@ -52,15 +63,14 @@ async public UniTask ModifyStateAndDisplay(Action stateModifie } /// - /// Displays the popup with a given state modifier and returns the response as a callback + /// Displays the popup with a given state modifier action and returns the response as a callback /// async public void ModifyStateAndDisplay(Action stateModifier, Action responseCallback) => responseCallback?.Invoke(await ModifyStateAndDisplay(stateModifier)); - protected override void HandleStateSet() => HandlePopupStateSet(); - - protected abstract void HandlePopupStateSet(); - protected abstract UniTask GetResponse(); + + [Obsolete(".HandleStateSet instead")] + protected virtual void HandlePopupStateSet() => OnStateSet(); } } diff --git a/Assets/Adrenak.UGX/Runtime/View/IconView.cs b/Assets/Adrenak.UGX/Runtime/View/IconView.cs deleted file mode 100644 index 5a37611..0000000 --- a/Assets/Adrenak.UGX/Runtime/View/IconView.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; -using UnityEngine.EventSystems; -using UnityEngine.Events; - -namespace Adrenak.UGX { - [Serializable] - public class IconViewState : ViewState { - public string text; - public Picture.Source source; - public string spriteImageURL; - public string spriteResourcePath; - public Sprite spriteAsset; - } - - public class IconView : StatefulView, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler { - public UnityEvent onClick = new UnityEvent(); - -#pragma warning disable 0649 - [SerializeField] UnityEvent onPointerEnter; - [SerializeField] UnityEvent onPointerExit; - [SerializeField] UnityEvent onPointerClick; - [SerializeField] Text text; - [SerializeField] Picture picture; -#pragma warning disable 0649 - - protected override void HandleStateSet() { - text.text = State.text; - picture.source = State.source; - - switch (State.source) { - case Picture.Source.Asset: - picture.sprite = State.spriteAsset; - break; - case Picture.Source.Resource: - picture.path = State.spriteResourcePath; - break; - case Picture.Source.URL: - picture.path = State.spriteImageURL; - break; - } - - picture.Refresh(); - } - - public void Click() => - onClick?.Invoke(); - - public void OnPointerExit(PointerEventData eventData) => - onPointerExit.Invoke(); - - public void OnPointerEnter(PointerEventData eventData) => - onPointerEnter.Invoke(); - - public void OnPointerClick(PointerEventData eventData) => - onPointerClick.Invoke(); - } -} diff --git a/Assets/Adrenak.UGX/Runtime/View/IconView.cs.meta b/Assets/Adrenak.UGX/Runtime/View/IconView.cs.meta deleted file mode 100644 index 4e7ace6..0000000 --- a/Assets/Adrenak.UGX/Runtime/View/IconView.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ad130ed2d0d6d4b49af641c7b446f8a1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Adrenak.UGX/Runtime/View/State.cs b/Assets/Adrenak.UGX/Runtime/View/State.cs deleted file mode 100644 index 29d3217..0000000 --- a/Assets/Adrenak.UGX/Runtime/View/State.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Adrenak.UGX{ - [Serializable] - public abstract class ViewState { - public string ID; - } -} \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Runtime/View/StatefulView.cs b/Assets/Adrenak.UGX/Runtime/View/StatefulView.cs index a6a1787..367568d 100644 --- a/Assets/Adrenak.UGX/Runtime/View/StatefulView.cs +++ b/Assets/Adrenak.UGX/Runtime/View/StatefulView.cs @@ -1,38 +1,86 @@ using UnityEngine; -using System.Collections; using NaughtyAttributes; using System; namespace Adrenak.UGX { + /// + /// A View with a state object + /// [Serializable] public abstract class StatefulView : View where TState : ViewState { - public event EventHandler OnViewStateSet; + /// + /// Fired when the field is set + /// + public event EventHandler StateSet; + /// + /// Whether the View should update itself with the state it starts with. + /// + [Tooltip("Whether the View should update itself with the state it starts with.")] [BoxGroup("View State")] public bool updateFromStateOnStart = false; - [BoxGroup("View State")] [SerializeField] TState currentState; + [Tooltip("Current state of the view. Changing values inside it will not trigger Update" + + "automatically. You must click the Update View button on this component to see the changes.")] + [BoxGroup("View State")] [SerializeField] TState state; + + /// + /// Current state of the View + /// public TState State { - get => currentState; + get => state; set { - currentState = value ?? throw new Exception(nameof(State)); - OnViewStateSet?.Invoke(this, currentState); - HandleStateSet(); + state = value ?? throw new Exception(nameof(State)); + StateSet?.Invoke(this, state); + OnStateSet(); } } + /// + /// If you inherit from this class and create a Start() method there, + /// make sure the first line of that method is base.Start() so that + /// StatefulView.Start() is called + /// protected void Start() { - if (updateFromStateOnStart) + if (updateFromStateOnStart) UpdateView(); + + try { + OnStart(); + } + catch { } } + /// + /// Updates the View using the current state + /// [Button("Update View")] public void UpdateView() { #if UNITY_EDITOR UnityEditor.Undo.RecordObject(gameObject, "Update View"); #endif - HandleStateSet(); + OnStateSet(); + } + + /// + /// Modifies the state based on the action injected and Updates the view + /// + /// An Action defining how the state should be modified + public void UpdateView(Action stateModification) { + stateModification?.Invoke(state); + UpdateView(); + } + + /// + /// Sets the state and Updates the view + /// + /// The new state to be used + public void UpdateView(TState state){ + State = state; + UpdateView(); } - protected abstract void HandleStateSet(); + protected abstract void OnStart(); + + protected abstract void OnStateSet(); } } \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Runtime/View/View.cs b/Assets/Adrenak.UGX/Runtime/View/View.cs index 139d287..c61b7d3 100644 --- a/Assets/Adrenak.UGX/Runtime/View/View.cs +++ b/Assets/Adrenak.UGX/Runtime/View/View.cs @@ -2,16 +2,26 @@ using System; namespace Adrenak.UGX { + /// + /// The fundamental class used to define anything that is visible + /// to the user. + /// [DisallowMultipleComponent] [Serializable] [RequireComponent(typeof(RectTransform))] public class View : UGXBehaviour { - public string viewID; + /// + /// ID that can be used to identify the View + /// + public string ID; - public static View operator / (View S1, string childName) { - var views = S1.GetComponentsInChildren(); + /// + /// Returns a child View with the given ID. + /// + public static View operator /(View me, string childID) { + var views = me.GetComponentsInChildren(); foreach (var view in views) - if (view.viewID.Equals(childName)) + if (view.ID.Equals(childID)) return view; return null; } diff --git a/Assets/Adrenak.UGX/Runtime/View/ViewList.cs b/Assets/Adrenak.UGX/Runtime/View/ViewList.cs index 7bb277d..b356046 100644 --- a/Assets/Adrenak.UGX/Runtime/View/ViewList.cs +++ b/Assets/Adrenak.UGX/Runtime/View/ViewList.cs @@ -7,58 +7,216 @@ using UnityEngine.UI; namespace Adrenak.UGX { + /// + /// A component that populates view templates used solely on state data. ViewList implements + /// allows editing the UI using List-list methods such as .Add, .Remove. + /// + /// The ViewState type that is used by the child Views [Serializable] - public abstract class ViewList : View, ICollection, IList where T : ViewState { - public int resizingCheckFrameStep = 2; + public abstract class ViewList : View, IReadOnlyList, IReadOnlyCollection, IEnumerable, ICollection, IList where T : ViewState { + /// + /// The List may often need resizing due to the child element sizes often being dynamic over time. + /// Large values may lead to late correction in size, but are performance friendly. + /// Small values will lead to near instantanous correction, but requires more frequent checking, making it heavier. + /// Set as 0 to disable. + /// + [Tooltip("The List may often need resizing due to the child element sizes often being dynamic over time." + + "Large values may lead to late correction in size, but are performance friendly." + + "Small values will lead to near instantanous correction, but requires more frequent checking, making it heavier." + + "Set as 0 to disable.")] + public int resizingCheckFrameStep = 5; + + /// + /// The parent under which the elements must be instantiated. + /// + [Tooltip("The parent under which the elements must be instantiated.")] [BoxGroup("Instantiation")] public Transform container = null; + + /// + /// Template of the element View. Can be prefab or GameObject. + /// + [Tooltip("Template of the element View. Can be prefab or GameObject.")] [BoxGroup("Instantiation")] public View template = null; - [BoxGroup("State")] [SerializeField] List statesList = new List(); + /// + /// Whether the list uses the states list in the Start() method to + /// populate the elements. This can be used to author predefined UI. + /// + [Tooltip("Whether the list uses the states list in the Start() method to" + + "populate the elements. This can be used to author predefined UI.")] + public bool populateOnStart = false; + + [Tooltip("The states of the current children views")] + [BoxGroup("State")] [SerializeField] List currentStates = new List(); List> Instantiated { get; } = new List>(); - public int Count => statesList.Count; + List>> childViewMethods = new List>>(); + + // ================================================ + // API / PUBLIC + // ================================================ + public int Count => currentStates.Count; public bool IsReadOnly => false; public T this[int index] { get { - if (index < 0 || index > statesList.Count - 1) + if (index < 0 || index > currentStates.Count - 1) throw new Exception("Index out of bounds"); - return statesList[index]; + return currentStates[index]; } set { - if (index < 0 || index > statesList.Count) + if (index < 0 || index > currentStates.Count) throw new Exception("Index out of bounds"); Insert(index, value); } } - bool populateOnStart; - void Awake() { - populateOnStart = !(statesList.Count == 0); - } + /// + /// Use to inject an action that runs on every view that has been instantiated. + /// + /// The action to run + /// Whether the action should be on future elements as well upons being created + public void InvokeAllChildViews(UnityAction> method, bool invokeOnFutureChildViews = true) { + Instantiated.ForEach(x => method(x)); + if (invokeOnFutureChildViews) + childViewMethods.Add(method); + } + + /// + /// Clears the state and instantiated child views + /// + public void Clear() { + foreach (var state in currentStates) + Destroy(state); + currentStates.Clear(); + } + + /// + /// Repopulates all the instances using the current state + /// + [Button] + public void Refresh() { + foreach (var state in currentStates) + Destroy(state); + PopulateFromCurrentStates(); + } + + /// + /// Returns if the List contains and element for the given state + /// + public bool Contains(T item) => currentStates.Contains(item); + + /// + /// Adds a state to the list and instantiates a view for it + /// + public void Add(T item) => Insert(currentStates.Count, item); + + /// + /// Adds a list of states and instantiate views for it + /// + /// + public void AddRange(List items) { + items.ForEach(x => Add(x)); + } + + /// + /// This method isn't implemented yet. + /// + public void CopyTo(T[] array, int arrayIndex) => + throw new NotImplementedException("ViewList doesn't support CopyTo yet."); + + /// + /// Removes the state as well as its corresponding View instance + /// + public bool Remove(T item) { + if (Contains(item)) { + Destroy(item); + currentStates.Remove(item); + return true; + } + return false; + } + + /// + /// Returns the index of the state in the list + /// + public int IndexOf(T item) { + return currentStates.IndexOf(item); + } + + /// + /// Inserts a state in the list, also moves its corersponding View instance + /// + /// + /// + public void Insert(int index, T item) { + if (item == null) + throw new Exception("Inserted item cannot be null"); + + if (index < 0) + throw new IndexOutOfRangeException("Insert method index cannot be negative"); + + if (index > 0 && index > currentStates.Count) + throw new IndexOutOfRangeException("Insert method index out of bounds"); + + var instance = Instantiate(item); + if (instance != null) { + if (index > 0) { + currentStates.Insert(index, item); + Instantiated.Insert(index, instance); + if (instance.transform.GetSiblingIndex() != index) + instance.transform.SetSiblingIndex(index); + } + else { + currentStates.Add(item); + Instantiated.Add(instance); + instance.transform.SetSiblingIndex(0); + } + } + } + + /// + /// Removes the state from the state list and destroys the corresponding View instance + /// + /// + public void RemoveAt(int index) { + if (index < 0 || index >= currentStates.Count) + throw new IndexOutOfRangeException("RemoveAt method index was out of range"); + + var vm = currentStates[index]; + Destroy(vm); + currentStates.RemoveAt(index); + } + public IEnumerator GetEnumerator() { + return (IEnumerator)new ViewListEnumerator(currentStates.ToArray()); + } + + // ================================================ + // UNITY ENIGNE LOOP + // ================================================ void Start() { - if (populateOnStart) { - statesList.ForEach(x => { - var instance = Instantiate(x); - if (instance != null) - Instantiated.Add(instance); - }); - } + if (populateOnStart) + PopulateFromCurrentStates(); + } + + void PopulateFromCurrentStates(){ + currentStates.ForEach(x => { + var instance = Instantiate(x); + if (instance != null) + Instantiated.Add(instance); + }); } void Update() { - if(Time.frameCount % resizingCheckFrameStep == 0) - ResizeIfRequired(); + TryResize(); } - int lastHeight; - int lastWidth; + int lastHeight, lastWidth, height, width; RectTransform childRT; - int height; - int width; - void ResizeIfRequired(){ - height = 0; - width = 0; + void TryResize() { + if (resizingCheckFrameStep <= 0 || Time.frameCount % resizingCheckFrameStep != 0) return; + + height = width = 0; foreach (Transform child in container) { childRT = child.GetComponent(); height += (int)childRT.sizeDelta.y; @@ -72,12 +230,9 @@ void ResizeIfRequired(){ lastWidth = width; } - UnityAction> subscription; - public void SubscribeToChildren(UnityAction> subscription) { - this.subscription = subscription; - Instantiated.ForEach(x => this.subscription(x)); - } - + // ================================================ + // INSTANCE MANAGEMENT + // ================================================ StatefulView Instantiate(T t) { // We don't initialize when the application isn't playing. // This sometimes happens with requests that are fullfilled after @@ -96,7 +251,7 @@ StatefulView Instantiate(T t) { instance.hideFlags = HideFlags.DontSave; (instance as StatefulView).State = t; - subscription?.Invoke(instance as StatefulView); + childViewMethods.ForEach(x => x?.Invoke(instance as StatefulView)); return instance as StatefulView; } @@ -111,74 +266,13 @@ void Destroy(T t) { } } - public void Clear() { - foreach (var state in statesList) - Destroy(state); - statesList.Clear(); - } - - public bool Contains(T item) => statesList.Contains(item); - - public void Add(T item) => Insert(statesList.Count, item); - - public void CopyTo(T[] array, int arrayIndex) => - throw new NotImplementedException("ViewList doesn't support CopyTo yet."); - - public bool Remove(T item) { - if (Contains(item)) { - Destroy(item); - statesList.Remove(item); - return true; - } - return false; - } - - public int IndexOf(T item) { - return statesList.IndexOf(item); - } - - public void Insert(int index, T item) { - if (item == null) - throw new Exception("Inserted item cannot be null"); - - if (index < 0) - throw new IndexOutOfRangeException("Insert method index cannot be negative"); - - if(index > 0 && index > statesList.Count) - throw new IndexOutOfRangeException("Insert method index out of bounds"); - - var instance = Instantiate(item); - if (instance != null) { - if(index > 0) { - statesList.Insert(index, item); - Instantiated.Insert(index, instance); - if (instance.transform.GetSiblingIndex() != index) - instance.transform.SetSiblingIndex(index); - } - else { - statesList.Add(item); - Instantiated.Add(instance); - instance.transform.SetSiblingIndex(0); - } - } - } - - public void RemoveAt(int index) { - if (index < 0 || index >= statesList.Count) - throw new IndexOutOfRangeException("RemoveAt method index was out of range"); - - var vm = statesList[index]; - Destroy(vm); - statesList.RemoveAt(index); - } - - public IEnumerator GetEnumerator() { - return (IEnumerator)new ViewListEnumerator(statesList.ToArray()); - } - IEnumerator IEnumerable.GetEnumerator() { - return (IEnumerator)new ViewListEnumerator(statesList.ToArray()); + return (IEnumerator)new ViewListEnumerator(currentStates.ToArray()); } + + [Obsolete("Use .RunPerChildView instead")] + public void SubscribeToChildren(UnityAction> subscription) => + InvokeAllChildViews(subscription, true); } // When you implement IEnumerable, you must also implement IEnumerator. diff --git a/Assets/Adrenak.UGX/Runtime/View/ViewState.cs b/Assets/Adrenak.UGX/Runtime/View/ViewState.cs new file mode 100644 index 0000000..3c60dd7 --- /dev/null +++ b/Assets/Adrenak.UGX/Runtime/View/ViewState.cs @@ -0,0 +1,14 @@ +using System; + +namespace Adrenak.UGX { + /// + /// Base class for View state definitions + /// + [Serializable] + public abstract class ViewState { + /// + /// An ID that can be used to identify it. Optional. + /// + public string ID; + } +} \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Runtime/View/State.cs.meta b/Assets/Adrenak.UGX/Runtime/View/ViewState.cs.meta similarity index 100% rename from Assets/Adrenak.UGX/Runtime/View/State.cs.meta rename to Assets/Adrenak.UGX/Runtime/View/ViewState.cs.meta diff --git a/Assets/Adrenak.UGX/Runtime/Window/Window.cs b/Assets/Adrenak.UGX/Runtime/Window/Window.cs index 9d4c41d..5d3a741 100644 --- a/Assets/Adrenak.UGX/Runtime/Window/Window.cs +++ b/Assets/Adrenak.UGX/Runtime/Window/Window.cs @@ -1,35 +1,63 @@ -using UnityEngine; +using System; +using UnityEngine; using System.Linq; using UnityEngine.Events; using Cysharp.Threading.Tasks; using NaughtyAttributes; namespace Adrenak.UGX { + /// + /// Keeps track of whether a UI is opening, opened, closing, closed. + /// Uses Tweeners to open (Transition Up) and close (Transition Down) + /// public class Window : UGXBehaviour { [ReadOnly] [SerializeField] WindowStatus status; + /// + /// The current status of the window + /// public WindowStatus Status { get => status; private set => status = value; } - public bool autoPopOnBack = true; - + /// + /// Fired when the window starts opening + /// public UnityEvent onWindowStartOpening; + + /// + /// Fired when the window is finished opening + /// public UnityEvent onWindowDoneOpening; + + /// + /// Fired when the window starts closing + /// public UnityEvent onWindowStartClosing; + + /// + /// Fired when the window is finished closing + /// public UnityEvent onWindowDoneClosing; + /// + /// Opens the window + /// [Button] async public void OpenWindow() => await OpenWindowAsync(); + /// + /// Opens the window and returns a task that completes when it's done opening + /// async public UniTask OpenWindowAsync() { await UniTask.SwitchToMainThread(); if (Status == WindowStatus.Opened || Status == WindowStatus.Opening) return; + OnWindowStartOpening(); onWindowStartOpening?.Invoke(); Status = WindowStatus.Opening; - var transitions = transitioners.Where(x => x.enabled) + var transitions = tweeners.Where(x => x.enabled) .Select(x => x.TransitionInAsync()) .ToList(); @@ -37,21 +65,31 @@ async public UniTask OpenWindowAsync() { await UniTask.SwitchToMainThread(); Status = WindowStatus.Opened; - WindowOpened(); + OnWindowDoneOpening(); onWindowDoneOpening?.Invoke(); +#pragma warning disable 0618 + WindowOpened(); +#pragma warning restore 0618 } + /// + /// Closes the window + /// [Button] async public void CloseWindow() => await CloseWindowAsync(); + /// + /// Closes the window and returns a task that completes when it's done closing + /// async public UniTask CloseWindowAsync() { await UniTask.SwitchToMainThread(); if (Status == WindowStatus.Closed || Status == WindowStatus.Closing) return; + OnWindowStartClosing(); onWindowStartClosing?.Invoke(); Status = WindowStatus.Closing; - var transitions = transitioners.Where(x => x.enabled) + var transitions = tweeners.Where(x => x.enabled) .Select(x => x.TransitionOutAsync()) .ToList(); @@ -59,11 +97,45 @@ async public UniTask CloseWindowAsync() { await UniTask.SwitchToMainThread(); Status = WindowStatus.Closed; - WindowClosed(); + OnWindowDoneClosing(); onWindowDoneClosing?.Invoke(); +#pragma warning disable 0618 + WindowClosed(); +#pragma warning restore 0618 } + /// + /// Called when the Window starts opening + /// + protected virtual void OnWindowStartOpening() { } + + /// + /// Called when the Window finishes opening + /// + protected virtual void OnWindowDoneOpening() { } + + /// + /// Called when the Window starts closing + /// + protected virtual void OnWindowStartClosing() { } + + /// + /// Called when the Window finishes closing + /// + protected virtual void OnWindowDoneClosing() { } + +#region OBSOLETE + /// + /// Called when the Window finishes opening + /// + [Obsolete("Use OnWindowDoneOpening instead")] protected virtual void WindowOpened() { } + + /// + /// Called when the Window finishes closing + /// + [Obsolete("Use OnWindowDoneClosing instead")] protected virtual void WindowClosed() { } +#endregion } } \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Runtime/Window/WindowFullscreenAddon.cs b/Assets/Adrenak.UGX/Runtime/Window/WindowFullscreenAddon.cs index 699c98b..0652f8a 100644 --- a/Assets/Adrenak.UGX/Runtime/Window/WindowFullscreenAddon.cs +++ b/Assets/Adrenak.UGX/Runtime/Window/WindowFullscreenAddon.cs @@ -1,6 +1,9 @@ using UnityEngine; namespace Adrenak.UGX { + /// + /// Allows to specify the full screen mode should when this window opens + /// [RequireComponent(typeof(Window))] public class WindowFullscreenAddon : UGXBehaviour { public bool isFullscreen; diff --git a/Assets/Adrenak.UGX/Runtime/Window/WindowScreenOrientationAddon.cs b/Assets/Adrenak.UGX/Runtime/Window/WindowScreenOrientationAddon.cs index c3fba08..503d9e0 100644 --- a/Assets/Adrenak.UGX/Runtime/Window/WindowScreenOrientationAddon.cs +++ b/Assets/Adrenak.UGX/Runtime/Window/WindowScreenOrientationAddon.cs @@ -1,6 +1,9 @@ using UnityEngine; namespace Adrenak.UGX { + /// + /// Allows to specify the screen orientation when this window opens + /// [RequireComponent(typeof(Window))] public class WindowScreenOrientationAddon : UGXBehaviour { public ScreenOrientation orientation; diff --git a/Assets/Adrenak.UGX/Runtime/Window/WindowStatus.cs b/Assets/Adrenak.UGX/Runtime/Window/WindowStatus.cs index c737a97..ac34229 100644 --- a/Assets/Adrenak.UGX/Runtime/Window/WindowStatus.cs +++ b/Assets/Adrenak.UGX/Runtime/Window/WindowStatus.cs @@ -1,4 +1,4 @@ -namespace Adrenak.UGX{ +namespace Adrenak.UGX { public enum WindowStatus { Closed, Closing, diff --git a/Assets/Adrenak.UGX/Runtime/_Misc/Texture2DDownloader.cs b/Assets/Adrenak.UGX/Runtime/_Misc/Texture2DDownloader.cs index 34832bf..f11f3a6 100644 --- a/Assets/Adrenak.UGX/Runtime/_Misc/Texture2DDownloader.cs +++ b/Assets/Adrenak.UGX/Runtime/_Misc/Texture2DDownloader.cs @@ -3,8 +3,12 @@ using System.Collections.Generic; using Adrenak.Unex; using UnityEngine; +using Cysharp.Threading.Tasks; namespace Adrenak.UGX { + /// + /// A coroutine based image downloader that returns the result as a Texture2D. + /// public class Texture2DDownloader : MonoBehaviour { class Request { public string path; @@ -19,7 +23,7 @@ public Request(string _path, Action _onSuccess, Action _on } int maxConcurrentDownloads; - float secondsPerTextureLoad; + float textureLoadPeriod; List pending = new List(); List ongoing = new List(); List textureLoads = new List(); @@ -27,16 +31,25 @@ public Request(string _path, Action _onSuccess, Action _on bool CanSendNewRequest => ongoing.Count < maxConcurrentDownloads; float loadTimer = 0; + /// + /// Cannot construct using 'new' keyword. Use .New method. + /// Texture2DDownloader() { } - public static Texture2DDownloader New(int _maxConcurrentDownloads = 5, float _secondsPerTextureLoad = .1f) { + /// + /// Creates a new instance. + /// + /// The maximum number of cocurrent downloads possible. + /// The minimum time between loading consecutive Texture2D objects from byte[] + /// + public static Texture2DDownloader New(int _maxConcurrentDownloads = 5, float _textureLoadPeriod = .1f) { var go = new GameObject("Texture2DDownloader"); DontDestroyOnLoad(go); go.hideFlags = HideFlags.DontSave; var instance = go.AddComponent(); instance.maxConcurrentDownloads = _maxConcurrentDownloads; - instance.secondsPerTextureLoad = _secondsPerTextureLoad; - instance.loadTimer = _secondsPerTextureLoad; + instance.textureLoadPeriod = _textureLoadPeriod; + instance.loadTimer = _textureLoadPeriod; return instance; } @@ -44,7 +57,7 @@ void Update() { DispatchRequests(); loadTimer += Time.unscaledDeltaTime; - if(loadTimer > secondsPerTextureLoad) { + if(loadTimer > textureLoadPeriod) { loadTimer = 0; DispatchTextureLoads(); } @@ -62,6 +75,12 @@ void DispatchTextureLoads() { } } + /// + /// Downloads an image from the URL and returns the results as callbacks + /// + /// The URL/path + /// Callback when the download is successful + /// Callback for when the download is unsuccessful public void Download(string path, Action onSuccess, Action onFailure) { var req = new Request(path, onSuccess, onFailure); if (CanSendNewRequest) { @@ -72,6 +91,20 @@ public void Download(string path, Action onSuccess, Action pending.Add(req); } + /// + /// Downloads an image form the URL and returns the results using UniTask + /// + /// + /// A UniTask instance + public UniTask Download(string path){ + var source = new UniTaskCompletionSource(); + Download(path, + result => source.TrySetResult(result), + exception => source.TrySetException(exception) + ); + return source.Task; + } + IEnumerator SendRequest(Request request) { pending.EnsureDoesntContain(request); ongoing.EnsureContains(request); diff --git a/Assets/Adrenak.UGX/Runtime/_Misc/UGXBehaviour.cs b/Assets/Adrenak.UGX/Runtime/_Misc/UGXBehaviour.cs index ab1ab33..399afa5 100644 --- a/Assets/Adrenak.UGX/Runtime/_Misc/UGXBehaviour.cs +++ b/Assets/Adrenak.UGX/Runtime/_Misc/UGXBehaviour.cs @@ -1,10 +1,14 @@ -using UnityEngine; +using System; +using UnityEngine; namespace Adrenak.UGX { [System.Serializable] [RequireComponent(typeof(RectTransform))] public class UGXBehaviour : MonoBehaviour { RectTransform rt; + /// + /// Returns the RectTransform of the GameObject. + /// public RectTransform RT { get { if (rt == null) @@ -13,19 +17,22 @@ public RectTransform RT { } } - public static T InstantiateUGXBehaviourResource(string path) where T : UGXBehaviour { - var resource = Resources.Load(path); - return Instantiate(resource); - } - + /// + /// Returns the View on this GameObject, if any + /// public View view => GetComponent(); + + /// + /// Returns the Window on this GameObject, if any + /// public Window window => GetComponent(); - public TweenerBase[] transitioners => GetComponents(); - public T Get() where T : TweenerBase { - foreach (var transitioner in transitioners) - if (transitioner is T) - return transitioner as T; - return null; - } + + /// + /// Returns all the Tweeners on this GameObject, if any + /// + public TweenerBase[] tweeners => GetComponents(); + + [Obsolete("Use .tweeners instead")] + public TweenerBase[] transitioners => tweeners; } } diff --git a/Assets/Adrenak.UGX/Runtime/_Misc/UGXExtensions.cs b/Assets/Adrenak.UGX/Runtime/_Misc/UGXExtensions.cs index faa79c4..df7a1d7 100644 --- a/Assets/Adrenak.UGX/Runtime/_Misc/UGXExtensions.cs +++ b/Assets/Adrenak.UGX/Runtime/_Misc/UGXExtensions.cs @@ -1,80 +1,115 @@ using System.Collections.Generic; -using System; using UnityEngine; using UnityEngine.UI; using Vector2 = UnityEngine.Vector2; namespace Adrenak.UGX { public static class UGXExtensions { - public static void SetColor(this Image image, Color color) { - image.color = color; - } - - public static void AddRange(this IList destination, IList source) { - foreach (var element in source) - destination.Add(element); - } - + /// + /// Returns the minimum distance RectTransform should move to the left + /// to get out of the bounds of the parent RectTransform. + /// public static float GetLeftExit(this RectTransform rt) { var parentRect = rt.parent.GetComponent().rect; return -parentRect.width / 2 - rt.rect.width / 2; } + /// + /// Returns the minimum distance RectTransform should move to the right + /// to get out of the bounds of the parent RectTransform. + /// public static float GetRightExit(this RectTransform rt) { var parentRect = rt.parent.GetComponent().rect; return parentRect.width / 2 + rt.rect.width / 2; } + /// + /// Returns the minimum distance RectTransform should move to the top + /// to get out of the bounds of the parent RectTransform. + /// public static float GetTopExit(this RectTransform rt) { var parentRect = rt.parent.GetComponent().rect; return parentRect.height / 2 + rt.rect.height / 2; } - public static float GetBottomExit(this RectTransform rt) { - var parentRect = rt.parent.GetComponent().rect; + /// + /// Returns the minimum distance RectTransform should move to the bottom + /// to get out of the bounds of the parent RectTransform. + /// + public static float GetBottomExit(this RectTransform rt, bool screen = false) { + Rect parentRect = rt.parent.GetComponent().rect; return -parentRect.width / 2 - rt.rect.height / 2; } + /// + /// Returns the x coordinate of the left edge of the RectTransform + /// public static float GetLeft(this RectTransform rt) { return rt.position.x - rt.rect.width * rt.lossyScale.x / 2; } + /// + /// Returns the x coordinate of the right edge of the RectTransform + /// public static float GetRight(this RectTransform rt) { return rt.position.x + rt.rect.width * rt.lossyScale.x / 2; } + /// + /// Returns the y coordinate of the top edge of the RectTransform + /// public static float GetTop(this RectTransform rt) { return rt.position.y + rt.rect.height * rt.lossyScale.y / 2; } + /// + /// Returns the y coordinate of the left edge of the RectTransform + /// public static float GetBottom(this RectTransform rt) { return rt.position.y - rt.rect.height * rt.lossyScale.y / 2; } + /// + /// Returns the coordinate of the top left corner of the RectTransform + /// public static Vector2 GetTopLeft(this RectTransform rt) { var left = rt.GetLeft(); var top = rt.GetTop(); return new Vector2(left, top); } + /// + /// Returns the coordinate of the top right corner of the RectTransform + /// public static Vector2 GetTopRight(this RectTransform rt) { var right = rt.GetRight(); var top = rt.GetTop(); return new Vector2(right, top); } + /// + /// Returns the coordinate of the bottom left corner of the RectTransform + /// public static Vector2 GetBottomLeft(this RectTransform rt) { var left = rt.GetLeft(); var bottom = rt.GetBottom(); return new Vector2(left, bottom); } + /// + /// Returns the coordinate of the bottom right corner of the RectTransform + /// public static Vector2 GetBottomRight(this RectTransform rt) { var right = rt.GetRight(); var bottom = rt.GetBottom(); return new Vector2(right, bottom); } + /// + /// Returns the visibility of the RectTransform + /// + /// + /// public static Visibility GetVisibility(this RectTransform rt) { var result = rt.IsVisible(out bool? partially); @@ -84,6 +119,10 @@ public static Visibility GetVisibility(this RectTransform rt) { return Visibility.Partial; } + /// + /// Returns if the RectTransform is visible + /// + /// If true, the RT is partially visible public static bool IsVisible(this RectTransform rt, out bool? partially) { var points = new Vector2[]{ rt.GetTopLeft(), @@ -156,6 +195,10 @@ public static bool IsVisible(this RectTransform rt, out bool? partially) { return true; } + /// + /// Set a KeyValuePair entry in the Dictionary. If the key exists, + /// the value is change. If not, the pair is added. + /// public static void SetPair(this IDictionary dict, K k, V v) { if (!dict.ContainsKey(k)) dict.Add(k, v); @@ -163,7 +206,12 @@ public static void SetPair(this IDictionary dict, K k, V v) { dict[k] = v; } - public static bool EnsureContains(this IDictionary dict, T t, K k) { + /// + /// Ensures the key is present in the dictionary. If it isn't, the provided + /// value is added along with the key. + /// + /// If the key was already present + public static bool EnsureKey(this IDictionary dict, T t, K k) { if (!dict.ContainsKey(t)) { dict.Add(t, k); return false; @@ -171,14 +219,28 @@ public static bool EnsureContains(this IDictionary dict, T t, K k) { return true; } - public static void EnsureContains(this List list, T t) { - if (!list.Contains(t)) + /// + /// Ensures the list contains the element. + /// + /// If the element was already present + public static bool EnsureContains(this List list, T t) { + if (!list.Contains(t)){ list.Add(t); + return false; + } + return true; } - public static void EnsureDoesntContain(this List list, T t) { - if (list.Contains(t)) + /// + /// Ensures that the list doesn't contain the element + /// + /// Returns true if the list DID contain the element + public static bool EnsureDoesntContain(this List list, T t) { + if (list.Contains(t)){ list.Remove(t); + return true; + } + return false; } } } \ No newline at end of file diff --git a/Assets/Adrenak.UGX/Runtime/_Misc/UGXInitializer.cs b/Assets/Adrenak.UGX/Runtime/_Misc/UGXInitializer.cs index 1548db1..f02dde6 100644 --- a/Assets/Adrenak.UGX/Runtime/_Misc/UGXInitializer.cs +++ b/Assets/Adrenak.UGX/Runtime/_Misc/UGXInitializer.cs @@ -3,7 +3,37 @@ using UnityEngine; namespace Adrenak.UGX { + /// + /// A utility MonoBehaviour to initialize UGX easily. Drag and drop in the scene, + /// the first scene of the game that loads on application start is preferred. + /// public class UGXInitializer : MonoBehaviour { + /// + /// The number of sprites the Picture cache will hold. The cache can overflow when + /// more sprites are required, but once the requirement goes down, the cache will + /// start to free up. + /// + [Tooltip("The number of sprites the Picture cache will hold. The cache can overflow when" + + "more sprites are required, but once the requirement goes down, the cache will" + + "start to free up.")] + public int pictureCacheSize = 50; + + /// + /// The maximum number of downloads that may occure simultaneously for Picture. + /// + [Tooltip("The maximum number of downloads that may occure simultaneously for Picture.")] + public int maxConcurrentPictureDownloads = 5; + + /// + /// The minumum time gap between two consecutive texture load. Settings a low value + /// may allow too many textures being loaded at once, causing major jitters. + /// Setting large values may cause the Pictures to be shown late. Recommended 1f - 1.5f + /// + [Tooltip("The minumum time gap between two consecutive texture load. Settings a low value " + + "may allow too many textures being loaded at once, causing major jitters." + + "Setting large values may cause the Pictures to be shown late. Recommended 1f - 1.5f")] + public float pictureTextureLoadPeriod = .125f; + static UGXInitializer instance; void Awake() { @@ -20,6 +50,10 @@ static void EnsureInstance() { public static void Init() { UnexInitializer.Initialize(); EnsureInstance(); + + var cache = new PictureMemoryCache(instance.pictureCacheSize); + PictureCacheBase.Downloader = Texture2DDownloader.New(instance.maxConcurrentPictureDownloads, instance.pictureTextureLoadPeriod); + Picture.Cache = new PictureMemoryCache(50); } } }