diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11ebc35..5a496ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,19 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
+## [0.7.0] - 2021-03-12
+
+- Added *NonDrawingView* to have an Image without a renderer to not add additional draw calls.
+- Added *SafeAreaHelperView* to add the possibility for the *RectTransform* to adjust himself to the screen notches
+- Added *AnimatedUiPresenter* to play animation on enter or closing
+- Added the possibility to add *Layers* externally into the *UiService*
+
+**Changed**:
+- Now *Canvas* are single *GameObjects* that can be controlled outside of the *UiService*
+
+**Fixed**:
+- Fixed the issue when setting data on *UiPresenterData* not being invoked
+
## [0.6.1] - 2020-09-24
- Updated dependency packages
diff --git a/Editor/NonDrawingViewEditor.cs b/Editor/NonDrawingViewEditor.cs
new file mode 100644
index 0000000..8a933d8
--- /dev/null
+++ b/Editor/NonDrawingViewEditor.cs
@@ -0,0 +1,26 @@
+using FirstLight.UiService;
+using UnityEditor;
+using UnityEditor.UI;
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+
+namespace FirstLightEditor.UiService
+{
+ ///
+ /// custom inspector
+ ///
+ [CanEditMultipleObjects, CustomEditor(typeof(NonDrawingView), false)]
+ public class NonDrawingViewEditor : GraphicEditor
+ {
+ public override void OnInspectorGUI ()
+ {
+ serializedObject.Update();
+ EditorGUILayout.PropertyField(m_Script, new GUILayoutOption[0]);
+
+ // skipping AppearanceControlsGUI
+ RaycastControlsGUI();
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/NonDrawingViewEditor.cs.meta b/Editor/NonDrawingViewEditor.cs.meta
new file mode 100644
index 0000000..e74e35e
--- /dev/null
+++ b/Editor/NonDrawingViewEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0d00a734d13e0448b855c59cd9381f86
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/AnimatedUiPresenter.cs b/Runtime/AnimatedUiPresenter.cs
new file mode 100644
index 0000000..947dc79
--- /dev/null
+++ b/Runtime/AnimatedUiPresenter.cs
@@ -0,0 +1,137 @@
+using System.Threading.Tasks;
+using UnityEngine;
+
+// ReSharper disable CheckNamespace
+
+namespace GameLovers.UiService
+{
+ ///
+ ///
+ /// Allows this Presenter to have an intro and outro animation when opened and closed to provide feedback and joy for players.
+ ///
+ [RequireComponent(typeof(CanvasGroup))]
+ public abstract class AnimatedUiPresenter : UiCloseActivePresenter
+ {
+ [SerializeField] protected CanvasGroup _canvasGroup;
+ [SerializeField] protected Animation _animation;
+ [SerializeField] protected AnimationClip _introAnimationClip;
+ [SerializeField] protected AnimationClip _outroAnimationClip;
+
+ private void OnValidate()
+ {
+ _canvasGroup = _canvasGroup ? _canvasGroup : GetComponent();
+
+ Debug.Assert(_animation != null, $"Presenter {gameObject.name} does not have a referenced Animation");
+ OnEditorValidate();
+ }
+
+ protected override async void OnClosed()
+ {
+ _animation.clip = _outroAnimationClip;
+ _animation.Play();
+
+ await Task.Delay(Mathf.RoundToInt(_animation.clip.length * 1000));
+
+ if (gameObject != null)
+ {
+ gameObject.SetActive(false);
+ OnClosedCompleted();
+ }
+ }
+
+ protected override async void OnOpened()
+ {
+ _canvasGroup.alpha = 0;
+ _animation.clip = _introAnimationClip;
+ _animation.Play();
+
+ await Task.Yield();
+
+ _canvasGroup.alpha = 1;
+
+ await Task.Delay(Mathf.RoundToInt(_animation.clip.length * 1000));
+
+ if (gameObject != null)
+ {
+ OnOpenedCompleted();
+ }
+ }
+
+ ///
+ /// Called in the end of this object MonoBehaviour's OnValidate() -> .
+ /// Override this method to have your custom extra validation.
+ ///
+ ///
+ /// This is Editor only call.
+ ///
+ protected virtual void OnEditorValidate() { }
+
+ ///
+ /// Called in the end of this object's .
+ /// Override this method to have your custom extra execution when the presenter is opened.
+ ///
+ protected virtual void OnOpenedCompleted() { }
+
+ ///
+ /// Called in the end of this object's .
+ /// Override this method to have your custom extra execution when the presenter is closed.
+ ///
+ protected virtual void OnClosedCompleted() { }
+ }
+
+ ///
+ ///
+ /// Allows this Presenter to have an intro and outro animation when opened and closed to provide feedback and joy for players.
+ ///
+ [RequireComponent(typeof(Animation), typeof(CanvasGroup))]
+ public abstract class AnimatedUiPresenterData : UiCloseActivePresenterData where T : struct
+ {
+ [SerializeField] protected Animation _animation;
+ [SerializeField] protected AnimationClip _introAnimationClip;
+ [SerializeField] protected AnimationClip _outroAnimationClip;
+
+ private void OnValidate()
+ {
+ Debug.Assert(_animation != null, $"Presenter {gameObject.name} does not have a referenced Animation");
+ OnEditorValidate();
+ }
+
+ protected override async void OnOpened()
+ {
+ _animation.clip = _introAnimationClip;
+ _animation.Play();
+
+ await Task.Delay(Mathf.RoundToInt(_animation.clip.length * 1000));
+
+ if (gameObject != null)
+ {
+ OnOpenedCompleted();
+ }
+ }
+
+ protected override async void OnClosed()
+ {
+ _animation.clip = _outroAnimationClip;
+ _animation.Play();
+
+ await Task.Delay(Mathf.RoundToInt(_animation.clip.length * 1000));
+
+ if (gameObject != null)
+ {
+ gameObject.SetActive(false);
+ OnClosedCompleted();
+ }
+ }
+
+
+ ///
+ protected virtual void OnEditorValidate() { }
+
+ ///
+ protected virtual void OnOpenedCompleted() { }
+
+ ///
+ protected virtual void OnClosedCompleted() { }
+ }
+}
+
diff --git a/Runtime/AnimatedUiPresenter.cs.meta b/Runtime/AnimatedUiPresenter.cs.meta
new file mode 100644
index 0000000..4a489ce
--- /dev/null
+++ b/Runtime/AnimatedUiPresenter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 739b63b617eca498f8eefa6fb150bd57
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/IUiService.cs b/Runtime/IUiService.cs
index 6ce9fc6..71c9ebb 100644
--- a/Runtime/IUiService.cs
+++ b/Runtime/IUiService.cs
@@ -14,9 +14,9 @@ namespace GameLovers.UiService
public interface IUiService
{
///
- /// Requests the of the given
+ /// Requests the root of the given
///
- Canvas GetLayer(int layer);
+ GameObject GetLayer(int layer);
///
/// Adds the given UI to the service
diff --git a/Runtime/NonDrawingView.cs b/Runtime/NonDrawingView.cs
new file mode 100644
index 0000000..cbf7e18
--- /dev/null
+++ b/Runtime/NonDrawingView.cs
@@ -0,0 +1,26 @@
+using UnityEngine.UI;
+
+// ReSharper disable CheckNamespace
+
+namespace FirstLight.UiService
+{
+ ///
+ /// A concrete subclass of the Unity UI `Graphic` class that just skips drawing.
+ /// Useful for providing a raycast target without actually drawing anything.
+ ///
+ public class NonDrawingView : Graphic
+ {
+ public override void SetMaterialDirty() { }
+ public override void SetVerticesDirty() { }
+
+ ///
+ /// Probably not necessary since the chain of calls
+ /// `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen.
+ /// So here really just as a fail-safe.
+ ///
+ protected override void OnPopulateMesh(VertexHelper vh)
+ {
+ vh.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/NonDrawingView.cs.meta b/Runtime/NonDrawingView.cs.meta
new file mode 100644
index 0000000..a60bb60
--- /dev/null
+++ b/Runtime/NonDrawingView.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6cd32b1e563d84059882296172ef8959
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/SafeAreaHelperView.cs b/Runtime/SafeAreaHelperView.cs
new file mode 100644
index 0000000..f17ae7b
--- /dev/null
+++ b/Runtime/SafeAreaHelperView.cs
@@ -0,0 +1,127 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+
+// ReSharper disable CheckNamespace
+
+namespace FirstLight.UiService
+{
+ ///
+ /// This view helper translate anchored views based on device safe area (screens witch a notch)
+ ///
+ [RequireComponent(typeof(RectTransform))]
+ public class SafeAreaHelperView : MonoBehaviour
+ {
+ private const float _floatTolerance = 0.01f;
+
+ [SerializeField] private RectTransform _rectTransform;
+ [SerializeField] private bool _ignoreWidth = true;
+ [SerializeField] private bool _onUpdate = false;
+ [SerializeField] private Vector2 _refResolution;
+
+ private Vector2 _initAnchoredPosition;
+ private Rect _resolution;
+ private Rect _safeArea;
+
+ internal void OnValidate()
+ {
+ _rectTransform = _rectTransform ? _rectTransform : GetComponent();
+ _refResolution = transform.root.GetComponent().referenceResolution;
+ _initAnchoredPosition = _rectTransform.anchoredPosition;
+ }
+
+ private void Awake()
+ {
+ _initAnchoredPosition = _rectTransform.anchoredPosition;
+ _resolution = new Rect(0,0, Screen.currentResolution.width, Screen.currentResolution.height);
+ _safeArea = Screen.safeArea;
+ }
+
+ private void OnEnable()
+ {
+ UpdatePositions();
+ }
+
+ private void Update()
+ {
+ if (_onUpdate)
+ {
+ UpdatePositions();
+ }
+ }
+
+ internal void UpdatePositions()
+ {
+ var anchorMax = _rectTransform.anchorMax;
+ var anchorMin = _rectTransform.anchorMin;
+ var anchoredPosition = _initAnchoredPosition;
+
+#if UNITY_EDITOR
+ // Because Unity Device Simulator and Game View have different screen resolution configs and sometimes use Desktop resolution
+ _safeArea = Screen.safeArea;
+ _resolution = new Rect(0, 0, Screen.width, Screen.height);
+ _resolution = _resolution == _safeArea ? _resolution : new Rect(0,0, Screen.currentResolution.width, Screen.currentResolution.height);
+#endif
+
+ if (_safeArea == _resolution)
+ {
+ return;
+ }
+
+ // Check if anchored to top or bottom
+ if (Math.Abs(anchorMax.y - anchorMin.y) < _floatTolerance)
+ {
+ // bottom
+ if (anchorMax.y < _floatTolerance)
+ {
+ anchoredPosition.y += (_safeArea.yMin - _resolution.yMin) * _refResolution.y / _resolution.height;
+ }
+ else // top
+ {
+ anchoredPosition.y += (_safeArea.yMax - _resolution.yMax) * _refResolution.y / _resolution.height;
+ }
+ }
+
+ // Check if anchored to left or right
+ if (!_ignoreWidth && Math.Abs(anchorMax.x - anchorMin.x) < _floatTolerance)
+ {
+ // left
+ if (anchorMax.x < _floatTolerance)
+ {
+ anchoredPosition.x += (_safeArea.xMin - _resolution.xMin) * _refResolution.x / _resolution.width;
+ }
+ else // right
+ {
+ anchoredPosition.x += (_safeArea.xMax - _resolution.xMax) * _refResolution.x / _resolution.width;
+ }
+ }
+
+ _rectTransform.anchoredPosition = anchoredPosition;
+ }
+ }
+
+ #if UNITY_EDITOR
+ [UnityEditor.CustomEditor(typeof(SafeAreaHelperView))]
+ public class SafeAreaHelperViewEditor : UnityEditor.Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+
+ if (GUILayout.Button("Update Anchored Data"))
+ {
+ var view = (SafeAreaHelperView) target;
+
+ view.OnValidate();
+ }
+
+ if (GUILayout.Button("Update Anchored View"))
+ {
+ var view = (SafeAreaHelperView) target;
+
+ view.UpdatePositions();
+ }
+ }
+ }
+ #endif
+}
\ No newline at end of file
diff --git a/Runtime/SafeAreaHelperView.cs.meta b/Runtime/SafeAreaHelperView.cs.meta
new file mode 100644
index 0000000..d9baaf0
--- /dev/null
+++ b/Runtime/SafeAreaHelperView.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4c59d679905ae4b039839721dcff1375
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/UiPresenter.cs b/Runtime/UiPresenter.cs
index 6229ac4..cddbe4b 100644
--- a/Runtime/UiPresenter.cs
+++ b/Runtime/UiPresenter.cs
@@ -17,11 +17,6 @@ public abstract class UiPresenter : MonoBehaviour
/// Requests the open status of the
///
public bool IsOpen => gameObject.activeSelf;
-
- ///
- /// Refreshes this opened UI
- ///
- public virtual void Refresh() {}
///
/// Allows the ui presenter implementation to have extra behaviour when it is initialized
@@ -58,13 +53,29 @@ internal void InternalOpen()
OnOpened();
}
- internal void InternalClose()
+ internal virtual void InternalClose()
+ {
+ if (this != null && gameObject != null)
+ {
+ gameObject.SetActive(false);
+ }
+ OnClosed();
+ }
+ }
+
+ ///
+ /// This type of UI Presenter closes a menu but does not disable the game object the Presenter is on.
+ /// The intention is for developers to implement subclasses with behaviour that turns off the game object after completing
+ /// some behaviour first, e.g. playing an animation or timeline.
+ ///
+ public abstract class UiCloseActivePresenter : UiPresenter
+ {
+ internal override void InternalClose()
{
- gameObject.SetActive(false);
OnClosed();
}
}
-
+
///
/// Tags the as a to allow defining a specific state when
/// opening the UI via the
@@ -85,11 +96,25 @@ public abstract class UiPresenterData : UiPresenter, IUiPresenterData where T
///
/// Allows the ui presenter implementation to have extra behaviour when the data defined for the presenter is set
///
- protected virtual void OnSetData(T data) {}
+ protected virtual void OnSetData() {}
internal void InternalSetData(T data)
{
Data = data;
+
+ OnSetData();
+ }
+ }
+
+ ///
+ /// Tags the as a to allow defining a specific state when
+ /// opening the UI via the
+ ///
+ public abstract class UiCloseActivePresenterData : UiPresenterData where T : struct
+ {
+ internal override void InternalClose()
+ {
+ OnClosed();
}
}
}
\ No newline at end of file
diff --git a/Runtime/UiService.cs b/Runtime/UiService.cs
index 46c721f..f93ab28 100644
--- a/Runtime/UiService.cs
+++ b/Runtime/UiService.cs
@@ -17,7 +17,7 @@ public class UiService : IUiServiceInit
private readonly IDictionary _uiConfigs = new Dictionary();
private readonly IDictionary _uiSets = new Dictionary();
private readonly IList _visibleUiList = new List();
- private readonly IList