Skip to content

Commit 713cb33

Browse files
author
Miguel Tomas
committed
Added NonDrawingView to have an Image without a renderer to not add adicional draw calls.
Added SafeAreaHelperView to add the possibility for the RectTransform to adjust himself to the screen notches Fixed the issue when setting data on UiPresenterData not being invoked. Added UiCloseActivePresenter to add the possibility to close the UiPresenter without deactivating the game object
1 parent 8206fc1 commit 713cb33

File tree

5 files changed

+231
-24
lines changed

5 files changed

+231
-24
lines changed

Editor/NonDrawingViewEditor.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using FirstLight.UiService;
2+
using UnityEditor;
3+
using UnityEditor.UI;
4+
using UnityEngine;
5+
6+
// ReSharper disable once CheckNamespace
7+
8+
namespace FirstLightEditor.UiService
9+
{
10+
/// <summary>
11+
/// <see cref="NonDrawingView"/> custom inspector
12+
/// </summary>
13+
[CanEditMultipleObjects, CustomEditor(typeof(NonDrawingView), false)]
14+
public class NonDrawingViewEditor : GraphicEditor
15+
{
16+
public override void OnInspectorGUI ()
17+
{
18+
serializedObject.Update();
19+
EditorGUILayout.PropertyField(m_Script, new GUILayoutOption[0]);
20+
21+
// skipping AppearanceControlsGUI
22+
RaycastControlsGUI();
23+
serializedObject.ApplyModifiedProperties();
24+
}
25+
}
26+
}

Runtime/NonDrawingView.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using UnityEngine.UI;
2+
3+
// ReSharper disable CheckNamespace
4+
5+
namespace FirstLight.UiService
6+
{
7+
/// <summary>
8+
/// A concrete subclass of the Unity UI `Graphic` class that just skips drawing.
9+
/// Useful for providing a raycast target without actually drawing anything.
10+
/// </summary>
11+
public class NonDrawingView : Graphic
12+
{
13+
public override void SetMaterialDirty() { }
14+
public override void SetVerticesDirty() { }
15+
16+
/// <summary>
17+
/// Probably not necessary since the chain of calls
18+
/// `Rebuild()`->`UpdateGeometry()`->`DoMeshGeneration()`->`OnPopulateMesh()` won't happen.
19+
/// So here really just as a fail-safe.
20+
/// </summary>
21+
protected override void OnPopulateMesh(VertexHelper vh)
22+
{
23+
vh.Clear();
24+
}
25+
}
26+
}

Runtime/SafeAreaHelperView.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using UnityEngine;
3+
using UnityEngine.UI;
4+
5+
// ReSharper disable CheckNamespace
6+
7+
namespace FirstLight.UiService
8+
{
9+
/// <summary>
10+
/// This view helper translate anchored views based on device safe area (screens witch a notch)
11+
/// </summary>
12+
[RequireComponent(typeof(RectTransform))]
13+
public class SafeAreaHelperView : MonoBehaviour
14+
{
15+
private const float _floatTolerance = 0.01f;
16+
17+
[SerializeField] private RectTransform _rectTransform;
18+
[SerializeField] private bool _ignoreWidth = true;
19+
[SerializeField] private bool _onUpdate = false;
20+
21+
private Vector2 _initAnchoredPosition;
22+
private Vector2 _refResolution;
23+
private Rect _resolution;
24+
private Rect _safeArea;
25+
26+
internal void OnValidate()
27+
{
28+
_rectTransform = _rectTransform ? _rectTransform : GetComponent<RectTransform>();
29+
_initAnchoredPosition = _rectTransform.anchoredPosition;
30+
}
31+
32+
private void Awake()
33+
{
34+
var resolution = Screen.currentResolution;
35+
36+
_refResolution = transform.root.GetComponent<CanvasScaler>().referenceResolution;
37+
_initAnchoredPosition = _rectTransform.anchoredPosition;
38+
_resolution = new Rect(0,0, resolution.width, resolution.height);
39+
_safeArea = Screen.safeArea;
40+
}
41+
42+
private void OnEnable()
43+
{
44+
UpdatePositions();
45+
}
46+
47+
private void Update()
48+
{
49+
if (_onUpdate)
50+
{
51+
UpdatePositions();
52+
}
53+
}
54+
55+
internal void UpdatePositions()
56+
{
57+
var anchorMax = _rectTransform.anchorMax;
58+
var anchorMin = _rectTransform.anchorMin;
59+
var anchoredPosition = _initAnchoredPosition;
60+
61+
#if UNITY_EDITOR
62+
// Because Unity Device Simulator and Game View have different screen resolution configs and sometimes use Desktop resolution
63+
_refResolution = transform.root.GetComponent<CanvasScaler>().referenceResolution;
64+
_safeArea = Screen.safeArea;
65+
_resolution = new Rect(0, 0, Screen.width, Screen.height);
66+
_resolution = _resolution == _safeArea ? _resolution : new Rect(0,0, Screen.currentResolution.width, Screen.currentResolution.height);
67+
#endif
68+
69+
if (_safeArea == _resolution)
70+
{
71+
return;
72+
}
73+
74+
// Check if anchored to top or bottom
75+
if (Math.Abs(anchorMax.y - anchorMin.y) < _floatTolerance)
76+
{
77+
// bottom
78+
if (anchorMax.y < _floatTolerance)
79+
{
80+
anchoredPosition.y += (_safeArea.yMin - _resolution.yMin) * _refResolution.y / _resolution.height;
81+
}
82+
else // top
83+
{
84+
anchoredPosition.y += (_safeArea.yMax - _resolution.yMax) * _refResolution.y / _resolution.height;
85+
}
86+
}
87+
88+
// Check if anchored to left or right
89+
if (!_ignoreWidth && Math.Abs(anchorMax.x - anchorMin.x) < _floatTolerance)
90+
{
91+
// left
92+
if (anchorMax.x < _floatTolerance)
93+
{
94+
anchoredPosition.x += (_safeArea.xMin - _resolution.xMin) * _refResolution.x / _resolution.width;
95+
}
96+
else // right
97+
{
98+
anchoredPosition.x += (_safeArea.xMax - _resolution.xMax) * _refResolution.x / _resolution.width;
99+
}
100+
}
101+
102+
_rectTransform.anchoredPosition = anchoredPosition;
103+
}
104+
}
105+
106+
#if UNITY_EDITOR
107+
[UnityEditor.CustomEditor(typeof(SafeAreaHelperView))]
108+
public class SafeAreaHelperViewEditor : UnityEditor.Editor
109+
{
110+
public override void OnInspectorGUI()
111+
{
112+
DrawDefaultInspector();
113+
114+
if (GUILayout.Button("Update Anchored Data"))
115+
{
116+
var view = (SafeAreaHelperView) target;
117+
118+
view.OnValidate();
119+
}
120+
121+
if (GUILayout.Button("Update Anchored View"))
122+
{
123+
var view = (SafeAreaHelperView) target;
124+
125+
view.UpdatePositions();
126+
}
127+
}
128+
}
129+
#endif
130+
}

Runtime/UiPresenter.cs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ public abstract class UiPresenter : MonoBehaviour
1717
/// Requests the open status of the <see cref="UiPresenter"/>
1818
/// </summary>
1919
public bool IsOpen => gameObject.activeSelf;
20-
21-
/// <summary>
22-
/// Refreshes this opened UI
23-
/// </summary>
24-
public virtual void Refresh() {}
2520

2621
/// <summary>
2722
/// Allows the ui presenter implementation to have extra behaviour when it is initialized
@@ -58,13 +53,29 @@ internal void InternalOpen()
5853
OnOpened();
5954
}
6055

61-
internal void InternalClose()
56+
internal virtual void InternalClose()
57+
{
58+
if (this != null && gameObject != null)
59+
{
60+
gameObject.SetActive(false);
61+
}
62+
OnClosed();
63+
}
64+
}
65+
66+
/// <summary>
67+
/// This type of UI Presenter closes a menu but does not disable the game object the Presenter is on.
68+
/// The intention is for developers to implement subclasses with behaviour that turns off the game object after completing
69+
/// some behaviour first, e.g. playing an animation or timeline.
70+
/// </summary>
71+
public abstract class UiCloseActivePresenter : UiPresenter
72+
{
73+
internal override void InternalClose()
6274
{
63-
gameObject.SetActive(false);
6475
OnClosed();
6576
}
6677
}
67-
78+
6879
/// <summary>
6980
/// Tags the <see cref="UiPresenter"/> as a <see cref="UiPresenterData{T}"/> to allow defining a specific state when
7081
/// opening the UI via the <see cref="UiService"/>
@@ -85,11 +96,25 @@ public abstract class UiPresenterData<T> : UiPresenter, IUiPresenterData where T
8596
/// <summary>
8697
/// Allows the ui presenter implementation to have extra behaviour when the data defined for the presenter is set
8798
/// </summary>
88-
protected virtual void OnSetData(T data) {}
99+
protected virtual void OnSetData() {}
89100

90101
internal void InternalSetData(T data)
91102
{
92103
Data = data;
104+
105+
OnSetData();
106+
}
107+
}
108+
109+
/// <summary>
110+
/// Tags the <see cref="UiCloseActivePresenter"/> as a <see cref="UiCloseActivePresenterData{T}"/> to allow defining a specific state when
111+
/// opening the UI via the <see cref="UiService"/>
112+
/// </summary>
113+
public abstract class UiCloseActivePresenterData<T> : UiPresenterData<T> where T : struct
114+
{
115+
internal override void InternalClose()
116+
{
117+
OnClosed();
93118
}
94119
}
95120
}

Runtime/UiService.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,7 @@ public void AddUi<T>(T uiPresenter, int layer, bool openAfter = false) where T :
7575
Presenter = uiPresenter
7676
};
7777

78-
for(int i = _layers.Count; i <= layer; i++)
79-
{
80-
var newObj = new GameObject($"Layer {i.ToString()}");
81-
var canvas = newObj.AddComponent<Canvas>();
82-
83-
newObj.transform.position = Vector3.zero;
84-
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
85-
canvas.sortingOrder = i;
86-
87-
_layers.Add(canvas);
88-
}
89-
9078
_uiViews.Add(reference.UiType, reference);
91-
uiPresenter.transform.SetParent(_layers[layer].transform);
9279
uiPresenter.Init(this);
9380

9481
if (openAfter)
@@ -140,13 +127,26 @@ public async Task<UiPresenter> LoadUiAsync(Type type, bool openAfter = false)
140127
{
141128
throw new KeyNotFoundException($"The UiConfig of type {type} was not added to the service. Call {nameof(AddUiConfig)} first");
142129
}
130+
131+
var layer = config.Layer;
132+
for(int i = _layers.Count; i <= layer; i++)
133+
{
134+
var newObj = new GameObject($"Layer {i.ToString()}");
135+
var canvas = newObj.AddComponent<Canvas>();
136+
137+
newObj.transform.position = Vector3.zero;
138+
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
139+
canvas.sortingOrder = i;
140+
141+
_layers.Add(canvas);
142+
}
143143

144-
var gameObject = await _assetLoader.InstantiatePrefabAsync(config.AddressableAddress, null, true);
144+
var gameObject = await _assetLoader.InstantiatePrefabAsync(config.AddressableAddress, _layers[layer].transform, false);
145145
var uiPresenter = gameObject.GetComponent<UiPresenter>();
146146

147147
gameObject.SetActive(false);
148148

149-
AddUi(uiPresenter, config.Layer, openAfter);
149+
AddUi(uiPresenter, layer, openAfter);
150150

151151
return uiPresenter;
152152
}

0 commit comments

Comments
 (0)