Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions Editor/NonDrawingViewEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using FirstLight.UiService;
using UnityEditor;
using UnityEditor.UI;
using UnityEngine;

// ReSharper disable once CheckNamespace

namespace FirstLightEditor.UiService
{
/// <summary>
/// <see cref="NonDrawingView"/> custom inspector
/// </summary>
[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();
}
}
}
11 changes: 11 additions & 0 deletions Editor/NonDrawingViewEditor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

137 changes: 137 additions & 0 deletions Runtime/AnimatedUiPresenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System.Threading.Tasks;
using UnityEngine;

// ReSharper disable CheckNamespace

namespace GameLovers.UiService
{
/// <inheritdoc />
/// <remarks>
/// Allows this Presenter to have an intro and outro animation when opened and closed to provide feedback and joy for players.
/// </remarks>
[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<CanvasGroup>();

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();
}
}

/// <summary>
/// Called in the end of this object MonoBehaviour's OnValidate() -> <see cref="OnValidate"/>.
/// Override this method to have your custom extra validation.
/// </summary>
/// <remarks>
/// This is Editor only call.
/// </remarks>
protected virtual void OnEditorValidate() { }

/// <summary>
/// Called in the end of this object's <see cref="OnOpened"/>.
/// Override this method to have your custom extra execution when the presenter is opened.
/// </summary>
protected virtual void OnOpenedCompleted() { }

/// <summary>
/// Called in the end of this object's <see cref="OnClosed"/>.
/// Override this method to have your custom extra execution when the presenter is closed.
/// </summary>
protected virtual void OnClosedCompleted() { }
}

/// <inheritdoc />
/// <remarks>
/// Allows this Presenter to have an intro and outro animation when opened and closed to provide feedback and joy for players.
/// </remarks>
[RequireComponent(typeof(Animation), typeof(CanvasGroup))]
public abstract class AnimatedUiPresenterData<T> : UiCloseActivePresenterData<T> 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();
}
}


/// <inheritdoc cref="AnimatedUiPresenter.OnEditorValidate"/>
protected virtual void OnEditorValidate() { }

/// <inheritdoc cref="AnimatedUiPresenter.OnOpenedCompleted"/>
protected virtual void OnOpenedCompleted() { }

/// <inheritdoc cref="AnimatedUiPresenter.OnClosedCompleted"/>
protected virtual void OnClosedCompleted() { }
}
}

11 changes: 11 additions & 0 deletions Runtime/AnimatedUiPresenter.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Runtime/IUiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ namespace GameLovers.UiService
public interface IUiService
{
/// <summary>
/// Requests the <see cref="Canvas"/> of the given <paramref name="layer"/>
/// Requests the root <see cref="GameObject"/> of the given <paramref name="layer"/>
/// </summary>
Canvas GetLayer(int layer);
GameObject GetLayer(int layer);

/// <summary>
/// Adds the given UI <paramref name="config"/> to the service
Expand Down
26 changes: 26 additions & 0 deletions Runtime/NonDrawingView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using UnityEngine.UI;

// ReSharper disable CheckNamespace

namespace FirstLight.UiService
{
/// <summary>
/// A concrete subclass of the Unity UI `Graphic` class that just skips drawing.
/// Useful for providing a raycast target without actually drawing anything.
/// </summary>
public class NonDrawingView : Graphic
{
public override void SetMaterialDirty() { }
public override void SetVerticesDirty() { }

/// <summary>
/// Probably not necessary since the chain of calls
/// `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen.
/// So here really just as a fail-safe.
/// </summary>
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
}
}
}
11 changes: 11 additions & 0 deletions Runtime/NonDrawingView.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 127 additions & 0 deletions Runtime/SafeAreaHelperView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using UnityEngine;
using UnityEngine.UI;

// ReSharper disable CheckNamespace

namespace FirstLight.UiService
{
/// <summary>
/// This view helper translate anchored views based on device safe area (screens witch a notch)
/// </summary>
[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<RectTransform>();
_refResolution = transform.root.GetComponent<CanvasScaler>().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
}
Loading