├── Core ├── AUILayer.cs ├── AUIScreenController.cs ├── ScreenControllerInterfaces.cs └── ScreenPropertyInterfaces.cs ├── Editor └── UIFrameworkTools.cs ├── LICENSE ├── MANUAL.md ├── Panel ├── APanelController.cs ├── PanelPriority.cs ├── PanelProperties.cs └── PanelUILayer.cs ├── README.md ├── ScreenTransitions ├── ATransitionComponent.cs ├── LegacyAnimationScreenTransition.cs └── SimpleFadeTransition.cs ├── UIFrame.cs ├── UISettings.cs └── Window ├── AWindowController.cs ├── WindowHistoryEntry.cs ├── WindowParaLayer.cs ├── WindowPriority.cs ├── WindowProperties.cs └── WindowUILayer.cs /Core/AUILayer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | namespace deVoid.UIFramework { 5 | /// 6 | /// Base class for UI Layers. Layers implement custom logic 7 | /// for Screen types when opening, closing etc. 8 | /// 9 | public abstract class AUILayer : MonoBehaviour where TScreen : IUIScreenController { 10 | protected Dictionary registeredScreens; 11 | 12 | /// 13 | /// Shows a screen 14 | /// 15 | /// The ScreenController to show 16 | public abstract void ShowScreen(TScreen screen); 17 | 18 | /// 19 | /// Shows a screen passing in properties 20 | /// 21 | /// The ScreenController to show 22 | /// The data payload 23 | /// The type of the data payload 24 | public abstract void ShowScreen(TScreen screen, TProps properties) where TProps : IScreenProperties; 25 | 26 | /// 27 | /// Hides a screen 28 | /// 29 | /// The ScreenController to be hidden 30 | public abstract void HideScreen(TScreen screen); 31 | 32 | /// 33 | /// Initialize this layer 34 | /// 35 | public virtual void Initialize() { 36 | registeredScreens = new Dictionary(); 37 | } 38 | 39 | /// 40 | /// Reparents the screen to this Layer's transform 41 | /// 42 | /// The screen controller 43 | /// The Screen Transform 44 | public virtual void ReparentScreen(IUIScreenController controller, Transform screenTransform) { 45 | screenTransform.SetParent(transform, false); 46 | } 47 | 48 | /// 49 | /// Register a ScreenController to a specific ScreenId 50 | /// 51 | /// Target ScreenId 52 | /// Screen Controller to be registered 53 | public void RegisterScreen(string screenId, TScreen controller) { 54 | if (!registeredScreens.ContainsKey(screenId)) { 55 | ProcessScreenRegister(screenId, controller); 56 | } 57 | else { 58 | Debug.LogError("[AUILayerController] Screen controller already registered for id: " + screenId); 59 | } 60 | } 61 | 62 | /// 63 | /// Unregisters a given controller from a ScreenId 64 | /// 65 | /// The ScreenId 66 | /// The controller to be unregistered 67 | public void UnregisterScreen(string screenId, TScreen controller) { 68 | if (registeredScreens.ContainsKey(screenId)) { 69 | ProcessScreenUnregister(screenId, controller); 70 | } 71 | else { 72 | Debug.LogError("[AUILayerController] Screen controller not registered for id: " + screenId); 73 | } 74 | } 75 | 76 | /// 77 | /// Attempts to find a registered screen that matches the id 78 | /// and shows it. 79 | /// 80 | /// The desired ScreenId 81 | public void ShowScreenById(string screenId) { 82 | TScreen ctl; 83 | if (registeredScreens.TryGetValue(screenId, out ctl)) { 84 | ShowScreen(ctl); 85 | } 86 | else { 87 | Debug.LogError("[AUILayerController] Screen ID " + screenId + " not registered to this layer!"); 88 | } 89 | } 90 | 91 | /// 92 | /// Attempts to find a registered screen that matches the id 93 | /// and shows it, passing a data payload. 94 | /// 95 | /// The Screen Id (by default, it's the name of the Prefab) 96 | /// The data payload for this screen to use 97 | /// The type of the Properties class this screen uses 98 | public void ShowScreenById(string screenId, TProps properties) where TProps : IScreenProperties { 99 | TScreen ctl; 100 | if (registeredScreens.TryGetValue(screenId, out ctl)) { 101 | ShowScreen(ctl, properties); 102 | } 103 | else { 104 | Debug.LogError("[AUILayerController] Screen ID " + screenId + " not registered!"); 105 | } 106 | } 107 | 108 | /// 109 | /// Attempts to find a registered screen that matches the id 110 | /// and hides it 111 | /// 112 | /// The id for this screen (by default, it's the name of the Prefab) 113 | public void HideScreenById(string screenId) { 114 | TScreen ctl; 115 | if (registeredScreens.TryGetValue(screenId, out ctl)) { 116 | HideScreen(ctl); 117 | } 118 | else { 119 | Debug.LogError("[AUILayerController] Could not hide Screen ID " + screenId + " as it is not registered to this layer!"); 120 | } 121 | } 122 | 123 | /// 124 | /// Checks if a screen is registered to this UI Layer 125 | /// 126 | /// The Screen Id (by default, it's the name of the Prefab) 127 | /// True if screen is registered, false if not 128 | public bool IsScreenRegistered(string screenId) { 129 | return registeredScreens.ContainsKey(screenId); 130 | } 131 | 132 | /// 133 | /// Hides all screens registered to this layer 134 | /// 135 | /// Should the screen animate while hiding? 136 | public virtual void HideAll(bool shouldAnimateWhenHiding = true) { 137 | foreach (var screen in registeredScreens) { 138 | screen.Value.Hide(shouldAnimateWhenHiding); 139 | } 140 | } 141 | 142 | protected virtual void ProcessScreenRegister(string screenId, TScreen controller) { 143 | controller.ScreenId = screenId; 144 | registeredScreens.Add(screenId, controller); 145 | controller.ScreenDestroyed += OnScreenDestroyed; 146 | } 147 | 148 | protected virtual void ProcessScreenUnregister(string screenId, TScreen controller) { 149 | controller.ScreenDestroyed -= OnScreenDestroyed; 150 | registeredScreens.Remove(screenId); 151 | } 152 | 153 | private void OnScreenDestroyed(IUIScreenController screen) { 154 | if (!string.IsNullOrEmpty(screen.ScreenId) 155 | && registeredScreens.ContainsKey(screen.ScreenId)) { 156 | UnregisterScreen(screen.ScreenId, (TScreen) screen); 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Core/AUIScreenController.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | 4 | namespace deVoid.UIFramework 5 | { 6 | /// 7 | /// Base implementation for UI Screens. You'll probably want to inherit 8 | /// from one of its child classes: AWindowController or APanelController, not this. 9 | /// 10 | /// 11 | /// 12 | public abstract class AUIScreenController : MonoBehaviour, IUIScreenController 13 | where TProps : IScreenProperties 14 | { 15 | [Header("Screen Animations")] 16 | [Tooltip("Animation that shows the screen")] 17 | [SerializeField] 18 | private ATransitionComponent animIn; 19 | 20 | [Tooltip("Animation that hides the screen")] 21 | [SerializeField] 22 | private ATransitionComponent animOut; 23 | 24 | [Header("Screen properties")] 25 | [Tooltip( 26 | "This is the data payload and settings for this screen. You can rig this directly in a prefab and/or pass it when you show this screen")] 27 | [SerializeField] 28 | private TProps properties; 29 | 30 | /// 31 | /// Unique identifier for this ID. If using the default system, it should be the same name as the screen's Prefab. 32 | /// 33 | public string ScreenId { get; set; } 34 | 35 | /// 36 | /// Transition component for the showing up animation 37 | /// 38 | public ATransitionComponent AnimIn 39 | { 40 | get { return animIn; } 41 | set { animIn = value; } 42 | } 43 | 44 | /// 45 | /// Transition component for the hiding animation 46 | /// 47 | public ATransitionComponent AnimOut 48 | { 49 | get { return animOut; } 50 | set { animOut = value; } 51 | } 52 | 53 | /// 54 | /// Occurs when "in" transition is finished. 55 | /// 56 | public Action InTransitionFinished { get; set; } 57 | 58 | /// 59 | /// Occurs when "out" transition is finished. 60 | /// 61 | public Action OutTransitionFinished { get; set; } 62 | 63 | /// 64 | /// Screen can fire this event to request its responsible layer to close it 65 | /// 66 | /// The close request. 67 | public Action CloseRequest { get; set; } 68 | 69 | /// 70 | /// If this screen is destroyed for some reason, it must warn its layer 71 | /// 72 | /// The destruction action. 73 | public Action ScreenDestroyed { get; set; } 74 | 75 | /// 76 | /// Is this screen currently visible? 77 | /// 78 | /// true if visible; otherwise, false. 79 | public bool IsVisible { get; private set; } 80 | 81 | /// 82 | /// The properties of this screen. Can contain 83 | /// serialized values, or passed in private values. 84 | /// 85 | /// The properties. 86 | protected TProps Properties 87 | { 88 | get { return properties; } 89 | set { properties = value; } 90 | } 91 | 92 | protected virtual void Awake() 93 | { 94 | AddListeners(); 95 | } 96 | 97 | protected virtual void OnDestroy() 98 | { 99 | if (ScreenDestroyed != null) 100 | { 101 | ScreenDestroyed(this); 102 | } 103 | 104 | InTransitionFinished = null; 105 | OutTransitionFinished = null; 106 | CloseRequest = null; 107 | ScreenDestroyed = null; 108 | RemoveListeners(); 109 | } 110 | 111 | /// 112 | /// For setting up all the listeners for events/messages. By default, called on Awake() 113 | /// 114 | protected virtual void AddListeners() 115 | { 116 | } 117 | 118 | /// 119 | /// For removing all the listeners for events/messages. By default, called on OnDestroy() 120 | /// 121 | protected virtual void RemoveListeners() 122 | { 123 | } 124 | 125 | /// 126 | /// When Properties are set for this screen, this method is called. 127 | /// At this point, you can safely access Properties. 128 | /// 129 | protected virtual void OnPropertiesSet() 130 | { 131 | } 132 | 133 | /// 134 | /// When the screen animates out, this is called 135 | /// immediately 136 | /// 137 | protected virtual void WhileHiding() 138 | { 139 | } 140 | 141 | /// 142 | /// When setting the properties, this method is called. 143 | /// This way, you can extend the usage of your properties by 144 | /// certain conditions. 145 | /// 146 | /// Properties. 147 | protected virtual void SetProperties(TProps props) 148 | { 149 | properties = props; 150 | } 151 | 152 | /// 153 | /// In case your screen has any special behaviour to be called 154 | /// when the hierarchy is adjusted 155 | /// 156 | protected virtual void HierarchyFixOnShow() 157 | { 158 | } 159 | 160 | /// 161 | /// Hides the screen 162 | /// 163 | /// Should animation be played? (defaults to true) 164 | public void Hide(bool animate = true) 165 | { 166 | DoAnimation(animate ? animOut : null, OnTransitionOutFinished, false); 167 | WhileHiding(); 168 | } 169 | 170 | /// 171 | /// Show this screen with the specified properties. 172 | /// 173 | /// The data for the screen. 174 | public void Show(IScreenProperties props = null) 175 | { 176 | if (props != null) 177 | { 178 | if (props is TProps) 179 | { 180 | SetProperties((TProps) props); 181 | } 182 | else 183 | { 184 | Debug.LogError("Properties passed have wrong type! (" + props.GetType() + " instead of " + 185 | typeof(TProps) + ")"); 186 | return; 187 | } 188 | } 189 | 190 | HierarchyFixOnShow(); 191 | OnPropertiesSet(); 192 | 193 | if (!gameObject.activeSelf) 194 | { 195 | DoAnimation(animIn, OnTransitionInFinished, true); 196 | } 197 | else 198 | { 199 | if (InTransitionFinished != null) 200 | { 201 | InTransitionFinished(this); 202 | } 203 | } 204 | } 205 | 206 | private void DoAnimation(ATransitionComponent caller, Action callWhenFinished, bool isVisible) 207 | { 208 | if (caller == null) 209 | { 210 | gameObject.SetActive(isVisible); 211 | if (callWhenFinished != null) 212 | { 213 | callWhenFinished(); 214 | } 215 | } 216 | else 217 | { 218 | if (isVisible && !gameObject.activeSelf) 219 | { 220 | gameObject.SetActive(true); 221 | } 222 | 223 | caller.Animate(transform, callWhenFinished); 224 | } 225 | } 226 | 227 | private void OnTransitionInFinished() 228 | { 229 | IsVisible = true; 230 | 231 | if (InTransitionFinished != null) 232 | { 233 | InTransitionFinished(this); 234 | } 235 | } 236 | 237 | private void OnTransitionOutFinished() 238 | { 239 | IsVisible = false; 240 | gameObject.SetActive(false); 241 | 242 | if (OutTransitionFinished != null) 243 | { 244 | OutTransitionFinished(this); 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /Core/ScreenControllerInterfaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace deVoid.UIFramework { 4 | /// 5 | /// Interface that all UI Screens must implement directly or indirectly 6 | /// 7 | public interface IUIScreenController { 8 | string ScreenId { get; set; } 9 | bool IsVisible { get; } 10 | 11 | void Show(IScreenProperties props = null); 12 | void Hide(bool animate = true); 13 | 14 | Action InTransitionFinished { get; set; } 15 | Action OutTransitionFinished { get; set; } 16 | Action CloseRequest { get; set; } 17 | Action ScreenDestroyed { get; set; } 18 | } 19 | 20 | /// 21 | /// Interface that all Windows must implement 22 | /// 23 | public interface IWindowController : IUIScreenController { 24 | bool HideOnForegroundLost { get; } 25 | bool IsPopup { get; } 26 | WindowPriority WindowPriority { get; } 27 | } 28 | 29 | /// 30 | /// Interface that all Panels must implement 31 | /// 32 | public interface IPanelController : IUIScreenController { 33 | PanelPriority Priority { get; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Core/ScreenPropertyInterfaces.cs: -------------------------------------------------------------------------------- 1 | namespace deVoid.UIFramework 2 | { 3 | /// 4 | /// Base interface for all the screen properties 5 | /// 6 | public interface IScreenProperties { } 7 | 8 | /// 9 | /// Base interface for all Panel properties 10 | /// 11 | public interface IPanelProperties : IScreenProperties 12 | { 13 | PanelPriority Priority { get; set; } 14 | } 15 | 16 | /// 17 | /// Base interface for Window properties. 18 | /// 19 | public interface IWindowProperties : IScreenProperties 20 | { 21 | WindowPriority WindowQueuePriority { get; set; } 22 | bool HideOnForegroundLost { get; set; } 23 | bool IsPopup { get; set; } 24 | bool SuppressPrefabProperties { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Editor/UIFrameworkTools.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.EventSystems; 7 | using UnityEngine.UI; 8 | 9 | namespace deVoid.UIFramework.Editor 10 | { 11 | public static class UIFrameworkTools 12 | { 13 | [MenuItem("Assets/Create/deVoid UI/UI Frame in Scene", priority = 2)] 14 | public static void CreateUIFrameInScene() { 15 | CreateUIFrame(); 16 | } 17 | 18 | [MenuItem("Assets/Create/deVoid UI/UI Frame Prefab", priority = 1)] 19 | public static void CreateUIFramePrefab() { 20 | var frame = CreateUIFrame(); 21 | 22 | string prefabPath = GetCurrentPath(); 23 | prefabPath = EditorUtility.SaveFilePanel("UI Frame Prefab", prefabPath,"UIFrame", "prefab"); 24 | 25 | if (prefabPath.StartsWith(Application.dataPath)) { 26 | prefabPath = "Assets" + prefabPath.Substring(Application.dataPath.Length); 27 | } 28 | 29 | if (!string.IsNullOrEmpty(prefabPath)) { 30 | CreateNewPrefab(frame, prefabPath); 31 | } 32 | 33 | Object.DestroyImmediate(frame); 34 | } 35 | 36 | private static GameObject CreateUIFrame() { 37 | var uiLayer = LayerMask.NameToLayer("UI"); 38 | var root = new GameObject("UIFrame"); 39 | var camera = new GameObject("UICamera"); 40 | 41 | var cam = camera.AddComponent(); 42 | cam.clearFlags = CameraClearFlags.Depth; 43 | cam.cullingMask = LayerMask.GetMask("UI"); 44 | cam.orthographic = true; 45 | cam.farClipPlane = 25; 46 | 47 | root.AddComponent(); 48 | var canvas = root.AddComponent(); 49 | root.layer = uiLayer; 50 | 51 | // ScreenSpaceCamera allows you to have things like 3d models, particles 52 | // and post-fx rendering out of the box (shader/render order limitations still apply) 53 | canvas.renderMode = RenderMode.ScreenSpaceCamera; 54 | canvas.worldCamera = cam; 55 | 56 | cam.transform.SetParent(root.transform, false); 57 | cam.transform.localPosition = new Vector3(0f, 0f, -1500f); 58 | 59 | var screenScaler = root.AddComponent(); 60 | screenScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; 61 | screenScaler.referenceResolution = new Vector2(1920, 1080); 62 | 63 | root.AddComponent(); 64 | 65 | var eventSystem = new GameObject("EventSystem"); 66 | eventSystem.transform.SetParent(root.transform, false); 67 | eventSystem.AddComponent(); 68 | eventSystem.AddComponent(); 69 | 70 | // Creating the layers 71 | var panelLayerGO = CreateRect("PanelLayer", root, uiLayer); 72 | var panelLayer = panelLayerGO.AddComponent(); 73 | 74 | var windowLayerGO = CreateRect("WindowLayer", root, uiLayer); 75 | var windowLayer = windowLayerGO.AddComponent(); 76 | 77 | var prioPanelLayer = CreateRect("PriorityPanelLayer", root, uiLayer); 78 | 79 | var windowParaLayerGO = CreateRect("PriorityWindowLayer", root, uiLayer); 80 | var windowParaLayer = windowParaLayerGO.AddComponent(); 81 | // setting the para layer via reflection 82 | SetPrivateField(windowLayer, windowParaLayer, "priorityParaLayer"); 83 | 84 | var darkenGO = CreateRect("DarkenBG", windowParaLayer.gameObject, uiLayer); 85 | var darkenImage = darkenGO.AddComponent(); 86 | darkenImage.color = new Color(0f, 0f, 0f, 0.75f); 87 | // setting the BG darkener via reflection 88 | SetPrivateField(windowParaLayer, darkenGO, "darkenBgObject"); 89 | darkenGO.SetActive(false); 90 | 91 | var tutorialPanelLayer = CreateRect("TutorialPanelLayer", root, uiLayer); 92 | 93 | // Rigging all the Panel Para-Layers on the Panel Layer 94 | var prioList = new List(); 95 | prioList.Add(new PanelPriorityLayerListEntry(PanelPriority.None, panelLayer.transform)); 96 | prioList.Add(new PanelPriorityLayerListEntry(PanelPriority.Prioritary, prioPanelLayer.transform)); 97 | prioList.Add(new PanelPriorityLayerListEntry(PanelPriority.Tutorial, tutorialPanelLayer.transform)); 98 | var panelPrios = new PanelPriorityLayerList(prioList); 99 | 100 | SetPrivateField(panelLayer, panelPrios, "priorityLayers"); 101 | 102 | return root; 103 | } 104 | 105 | public static string GetCurrentPath() { 106 | string path = AssetDatabase.GetAssetPath(Selection.activeObject); 107 | if (path == "") { 108 | path = "Assets"; 109 | } 110 | else if (Path.GetExtension(path) != "") { 111 | path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), ""); 112 | } 113 | 114 | return path; 115 | } 116 | 117 | private static void SetPrivateField(object target, object element, string fieldName) { 118 | var prop = target.GetType().GetField(fieldName, 119 | System.Reflection.BindingFlags.NonPublic 120 | | System.Reflection.BindingFlags.Instance); 121 | prop.SetValue(target, element); 122 | } 123 | 124 | private static GameObject CreateRect(string name, GameObject parentGO, int layer) { 125 | var parent = parentGO.GetComponent(); 126 | var newRect = new GameObject(name, typeof(RectTransform)); 127 | newRect.layer = layer; 128 | var rt = newRect.GetComponent(); 129 | 130 | rt.anchoredPosition = parent.position; 131 | rt.anchorMin = new Vector2(0, 0); 132 | rt.anchorMax = new Vector2(1, 1); 133 | rt.pivot = new Vector2(0.5f, 0.5f); 134 | rt.transform.SetParent(parent, false); 135 | rt.sizeDelta = Vector3.zero; 136 | 137 | return newRect; 138 | } 139 | 140 | private static void CreateNewPrefab(GameObject obj, string localPath) { 141 | PrefabUtility.SaveAsPrefabAsset(obj, localPath); 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yanko Oliveira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANUAL.md: -------------------------------------------------------------------------------- 1 | # deVoid UI Framework: Manual 2 | ## Part 1: Structure 3 | The main entry point in the system is the ***UI Frame***. It is the way you interact with the framework from your code. Internally, it serves as a facade to the ***UI Layers***. Layers are responsible for containing and handling ***Screens***. Screens can be of two types: 4 | * ***Panels*** are ad-hoc pieces of UI that are not bound to a specific history and can be visible and interactable at the same time. Eg: a HUD could be a *Panel*. 5 | * ***Windows*** are elements that usually fill up most of your display's real estate, follow a history and are the main interaction spots at a given time: you won't ever have two windows being interactable at the same time, but you might have one or more *Panels* and a single *Window* open. 6 | * ***Pop-Ups*** are a special case of Window that works the same way, but is displayed on top of the rest of the UI - it's a modal window. 7 | 8 | Screens can contain one or more ***Widgets*** which are self-contained components that can be reused across multiple scenarios. ***Layers*** can also make use of ***Para-Layers***, which are used to organize render order and display priority. In the default implementation, there is one Window para-layer (which is used to house pop-ups) and multiple Panel para-layers. 9 | 10 | For a Screen to be usable by the Framework, it needs to be ***Registered*** to a Layer. This operation is performed via the ***UI Frame***. 11 | 12 | You can use the built-in ***UI Settings*** file to rig your UI Frame prefab and the screens to be registered, then get a fully initialized instance. 13 | 14 | Since every UI Frame is its own self contained structure, this means that you can have multiple complete UIs at the same time, including in worldspace. You can also register it as a centralized service if it better fits your purpose, or you can mix and match, eg: you have a centralized UI that is for the general Game UI, that is permanent, but you have separate UIs for players that support split screen and have their own specific logic, and get created and destroyed with the players. 15 | 16 | The best way to understand these concepts is by checking them out in practice, so make sure to take a look at the [examples repo](https://github.com/yankooliveira/uiframework_examples). If you're feeling adventurous, you can always read the [blog post](http://yankooliveira.com/index.php/2017/12/27/uisystem/) where I originally described the rationale for the architecture in detail. 17 | 18 | ## Part 2: Implementing a Screen 19 | Every screen is identified by an unique ***Screen Id***. This id can be any `string`, but I usually use the name of the screen's Prefab. 20 | 21 | The first thing is deciding if you're implementing a ***Panel*** (implemented by extending an `APanelController` or a ***Window*** (implemented by extending an `AWindowController`). The `` parameter is optional: it's a type-safe ***Properties*** class (extended from `WindowProperties` or `PanelProperties`), which are data payloads that are passed onto screens when opening. 22 | 23 | You can reuse the same `IScreenController` across prefabs, and you can even use the same prefab multiple times, but *ScreenIds* must be unique. The implementation itself can be empty, but the most important method to know about is `OnPropertiesSet()`. This method is called as soon as the screen has had its `Properties` member set, which means it's your main entry point for filling up the screen with data. 24 | 25 | Eg: 26 | ```c# 27 | public class PlayerWindowController : AWindowController 28 | { 29 | protected override void OnPropertiesSet() { // At this point, Properties is guaranteed 30 | UpdateData(Properties.PlayerData); // to have the data passed by OpenWindow() 31 | } 32 | } 33 | ``` 34 | But you could also have something like 35 | ```c# 36 | public class EmptyWindowController : AWindowController { } 37 | ``` 38 | **IMPORTANT**: when implementing your Properties class, make sure it's tagged as `[System.Serializable]`, otherwise you won't be able to set it up on the prefab. 39 | 40 | If no properties are passed to a Screen displaying method, it will use the ones set up in the prefab. This means that you can optionally store values for the properties directly on a prefab as well. 41 | 42 | Other important methods are `AddListeners()` and `RemoveListeners()`. These are, by default, called on Awake and OnDestroy. These are the places where you can hook for events, and are useful entry points for eg: an UI that is responsive to Signals. 43 | 44 | Finally, `Close()` is a method that you can call from your UI to close itself - it is however not called when the screen is closed by something else - you can use `WhileHiding()` for cleanup operations that you might need to perform as a window closes. 45 | 46 | Check the `AUIScreenController` code for a list of all the virtual methods. 47 | 48 | **IMPORTANT:** While you can use the native Unity callbacks (`Awake`, `OnDestroy` etc), be aware that some of those are used for initialization on the abstract class code. So don't forget to always call the base methods when overriding those. 49 | 50 | ## Part 3: Operating Screens 51 | To Register a Screen, you can use `UIFrame.RegisterWindow`, `UIFrame.RegisterPanel` or `UIFrame.RegisterScreen`, which tries to infer what kind of Screen that is. 52 | 53 | After screens are registered, you can open them by using 54 | ```c# 55 | public void ShowPanel(string id, T properties) where T : IPanelProperties 56 | public void OpenWindow(string id, T properties) where T : IWindowProperties 57 | ``` 58 | or the parameterless versions, if your screen doesn't need a property payload 59 | ```c# 60 | public void ShowPanel(string id) 61 | public void OpenWindow(string id) 62 | ``` 63 | The analogous methods are 64 | ```c# 65 | public void HidePanel(string id) 66 | public void CloseWindow(string id) 67 | ``` 68 | Panels can be shown and hidden at any time, but Windows will obey the history stack and, if you try to close a Window that is not the currently open one, the system will issue a warning (this usually means you're trying to execute a logically invalid operation in your screen flow). 69 | 70 | If you have the need to un-register screens (eg: if you're doing manual memory management), you can use `UnregisterPanel()` and `UnregisterWindow()`. 71 | 72 | ### Addendum: default Properties setup 73 | For `Panels`, the only built-in Property is their *Priority*. This defines to which ***Para-Layer*** the Panel is parented to (which, in turn, defines it's render priority, or "what is drawn in front of what"). 74 | 75 | For `Windows`, there are additional default parameters that are important to keep in mind when setting up your Prefab: 76 | * **Hide On Foreground Lost**: this defines if this window should be hidden when another Window is opened. In practice, this defines if you have a visible "stack" of windows, or if opening a new one hides the current one. 77 | * **Window Priority**: this defines if the Window should always open on top when the `Open()` command is issued. If it's set to `Force Foreground`, it will open on top; if it's set to `None`, it will be enqueued and will be displayed as soon as the current Window is closed. 78 | * **Is Popup**: this defines if the window should work as a Pop-up. If marked, this will be parented to the `WindowPriorityLayer`, and it will automatically have a darkened background displayed under it. 79 | 80 | The default Layer order is 81 | 1. *PanelLayer* (Panels with no priority) 82 | 2. *WindowLayer* (regular Windows) 83 | 3. *PriorityPanelLayer* (Panels tagged as Prioritary) 84 | 4. *PriorityWindowLayer* (Pop-ups) 85 | 5. *[Other Panel para-layers]* 86 | ## Part 4: Transition animations 87 | Every Screen has two default members called ``AnimIn`` and ``AnimOut``. These can be rigged with any `ATransitionComponent`, which are classes that have to implement the following interface: 88 | ```c# 89 | public abstract void Animate(Transform target, Action callWhenFinished); 90 | ``` 91 | These are called by the ***Layer*** code when opening and closing a Screen, and the `callWhenFinished` callback basically tells the Layer that that transition is finished. Since a very common bug in mobile games is the user interacting with the UI while there are screen transitions happening, the `WindowLayer` automatically blocks user input by disabling the UIFrame's main `GraphicRaycaster` if there are any animations playing. This behaviour is not enforced by the `PanelLayer`. 92 | 93 | Creating multiple of these components can give your UI artists the flexibility in rigging and tweaking your UI animations, by mixing and matching multiple options. If no `ATransitionComponent` is rigged to a given transition, the screen's GameObject will simply be `SetActive(true/false)`. 94 | 95 | ## Epilogue 96 | I've tried to comment the source code thoroughly, so I hope that this manual, the [examples repo](https://github.com/yankooliveira/uiframework_examples), the [blog post](http://yankooliveira.com/index.php/2017/12/27/uisystem/) but, most importantly, the code itself, will be enough for you to get up and running. Good luck, hope it's useful and even though I don't know how quickly I'd be able to fix them, let me know if you find any major bugs [@yankooliveira](http://twitter.com/yankooliveira)! -------------------------------------------------------------------------------- /Panel/APanelController.cs: -------------------------------------------------------------------------------- 1 | namespace deVoid.UIFramework { 2 | /// 3 | /// Base class for panels that need no special Properties 4 | /// 5 | public abstract class APanelController : APanelController { } 6 | 7 | /// 8 | /// Base class for Panels 9 | /// 10 | public abstract class APanelController : AUIScreenController, IPanelController where T : IPanelProperties { 11 | public PanelPriority Priority { 12 | get { 13 | if (Properties != null) { 14 | return Properties.Priority; 15 | } 16 | else { 17 | return PanelPriority.None; 18 | } 19 | } 20 | } 21 | 22 | protected sealed override void SetProperties(T props) { 23 | base.SetProperties(props); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Panel/PanelPriority.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace deVoid.UIFramework 5 | { 6 | /// 7 | /// Defines to which para-layer 8 | /// the panel is going to be parented to 9 | /// 10 | public enum PanelPriority { 11 | None = 0, 12 | Prioritary = 1, 13 | Tutorial = 2, 14 | Blocker = 3, 15 | } 16 | 17 | [System.Serializable] 18 | public class PanelPriorityLayerListEntry { 19 | [SerializeField] 20 | [Tooltip("The panel priority type for a given target para-layer")] 21 | private PanelPriority priority; 22 | [SerializeField] 23 | [Tooltip("The GameObject that should house all Panels tagged with this priority")] 24 | private Transform targetParent; 25 | 26 | public Transform TargetParent { 27 | get { return targetParent; } 28 | set { targetParent = value; } 29 | } 30 | 31 | public PanelPriority Priority { 32 | get { return priority; } 33 | set { priority = value; } 34 | } 35 | 36 | public PanelPriorityLayerListEntry(PanelPriority prio, Transform parent) { 37 | priority = prio; 38 | targetParent = parent; 39 | } 40 | } 41 | 42 | [System.Serializable] 43 | public class PanelPriorityLayerList { 44 | [SerializeField] 45 | [Tooltip("A lookup of GameObjects to store panels depending on their Priority. Render priority is set by the hierarchy order of these GameObjects")] 46 | private List paraLayers = null; 47 | 48 | private Dictionary lookup; 49 | 50 | public Dictionary ParaLayerLookup { 51 | get { 52 | if (lookup == null || lookup.Count == 0) { 53 | CacheLookup(); 54 | } 55 | 56 | return lookup; 57 | } 58 | } 59 | 60 | private void CacheLookup() { 61 | lookup = new Dictionary(); 62 | for (int i = 0; i < paraLayers.Count; i++) { 63 | lookup.Add(paraLayers[i].Priority, paraLayers[i].TargetParent); 64 | } 65 | } 66 | 67 | public PanelPriorityLayerList(List entries) { 68 | paraLayers = entries; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Panel/PanelProperties.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace deVoid.UIFramework { 4 | /// 5 | /// Properties common to all panels 6 | /// 7 | [System.Serializable] 8 | public class PanelProperties : IPanelProperties { 9 | [SerializeField] 10 | [Tooltip("Panels go to different para-layers depending on their priority. You can set up para-layers in the Panel Layer.")] 11 | private PanelPriority priority; 12 | 13 | public PanelPriority Priority { 14 | get { return priority; } 15 | set { priority = value; } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Panel/PanelUILayer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace deVoid.UIFramework { 6 | /// 7 | /// This Layer controls Panels. 8 | /// Panels are Screens that have no history or queuing, 9 | /// they are simply shown and hidden in the Frame 10 | /// eg: a HUD, an energy bar, a mini map etc. 11 | /// 12 | public class PanelUILayer : AUILayer { 13 | [SerializeField] 14 | [Tooltip("Settings for the priority para-layers. A Panel registered to this layer will be reparented to a different para-layer object depending on its Priority.")] 15 | private PanelPriorityLayerList priorityLayers = null; 16 | 17 | public override void ReparentScreen(IUIScreenController controller, Transform screenTransform) { 18 | var ctl = controller as IPanelController; 19 | if (ctl != null) { 20 | ReparentToParaLayer(ctl.Priority, screenTransform); 21 | } 22 | else { 23 | base.ReparentScreen(controller, screenTransform); 24 | } 25 | } 26 | 27 | public override void ShowScreen(IPanelController screen) { 28 | screen.Show(); 29 | } 30 | 31 | public override void ShowScreen(IPanelController screen, TProps properties) { 32 | screen.Show(properties); 33 | } 34 | 35 | public override void HideScreen(IPanelController screen) { 36 | screen.Hide(); 37 | } 38 | 39 | public bool IsPanelVisible(string panelId) { 40 | IPanelController panel; 41 | if (registeredScreens.TryGetValue(panelId, out panel)) { 42 | return panel.IsVisible; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | private void ReparentToParaLayer(PanelPriority priority, Transform screenTransform) { 49 | Transform trans; 50 | if (!priorityLayers.ParaLayerLookup.TryGetValue(priority, out trans)) { 51 | trans = transform; 52 | } 53 | 54 | screenTransform.SetParent(trans, false); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deVoid UI Framework 2 | ![Example Project](https://img.itch.zone/aW1nLzE5NTQzODYuZ2lm/original/%2BrlumK.gif) 3 | 4 | *You can see a live demo of the [the examples repo](https://github.com/yankooliveira/uiframework_examples) at my [itch.io page](https://yanko.itch.io/devui)* 5 | 6 | **TL;DR:** 7 | *Step 1:* 8 | 9 | Right click on project view 10 | `Create -> deVoid UI -> UIFrame Prefab` 11 | then drag the Prefab onto your scene 12 | 13 | *Step 2:* 14 | Get a reference to your UI Frame and register some screens 15 | ```c# 16 | uiFrame.RegisterScreen("YourScreenId", yourScreenPrefab); 17 | ``` 18 | 19 | *Step 3:* 20 | Show your screens 21 | ```c# 22 | uiFrame.OpenWindow("YourWindowId"); 23 | uiFrame.ShowPanel("YourPanelId"); 24 | ``` 25 | 26 | Or, if you have some data payload 27 | 28 | ```c# 29 | uiFrame.OpenWindow("YourWindowId", yourWindowProperties); 30 | uiFrame.ShowPanel("YourPanelId", yourPanelProperties); 31 | ``` 32 | 33 | *Step 4:* 34 | Now that you're familiar with the API, right click on project view 35 | `Create -> deVoid UI -> UI Settings` 36 | 37 | Rig your UI Frame prefab as the UI Template, drag all your screens in the Screens To Register list and simply do 38 | 39 | ```c# 40 | uiFrame = yourUiSettings.CreateUIInstance(); 41 | ``` 42 | 43 | Which will give you a new UI Frame instance and automatically do *Step 2* for you. 44 | 45 | Make sure to check out [the examples repo](https://github.com/yankooliveira/uiframework_examples) and read [the manual](https://github.com/yankooliveira/uiframework/blob/master/MANUAL.md)! 46 | 47 | ## What? 48 | The *deVoid UI Framework* (or **devUI** for short) is a simple architecture for UI handling and navigation in Unity. It enforces one simple rule: *you can **never** directly access the internals of your UI code from external code*, but anything else is fair game. You want to read data from Singletons from inside your UI? Sure. You want to sandwich mediators, views and controllers and do MVC by the book? Go for it. You want to skip the data passing functionalities and use your own MVVM framework? Power to you. Do whatever floats your boat and works best for your needs. 49 | 50 | ## Why? 51 | Having worked with mobile F2P games for years, I've done my fair share of UI. And I *hate* doing UI. 52 | So I figured the more people learn how to do it, the smaller the chances that I ever have to do it again 🌈 53 | 54 | *(Also, sharing is caring and I'm secretly a hippie)* 55 | 56 | ### Features 57 | * Simple to extend and customize 58 | * Support for navigation history and queuing 59 | * Transition animations 60 | * Blocking user input while screens transition (especially handy for touch input) 61 | * Priority and layering 62 | * Focus on type safety and flexibility 63 | 64 | ### Known Limitations 65 | * No built in support for controller navigation 66 | 67 | ### Disclaimer: 68 | This is **A** solution to work with UI - I don't believe in perfect, one-size-fits-all solutions (and nor should you in anyone who tells you otherwise). But this architecture was battle tested in more than one game over the last few years (from game jams to medium and bigger sized games), and it ticked all the boxes for me so far. 69 | 70 | While I tried to keep this it as flexible and as easy to deal with as possible, there is still some structure to be followed. I recommend checking out [the examples repo](https://github.com/yankooliveira/uiframework_examples) and taking a look at [the manual](https://github.com/yankooliveira/uiframework/blob/master/MANUAL.md) before using. 71 | 72 | Although the architecture itself is sound and was tested in live environments, this implementation was made from scratch on my free time. I have been using it for a few months now and did some cleanup and bugfixes for the public release, so it *Should Work(TM)*. 73 | 74 | ### Acknowledgements 75 | A lot of the structure is inspired by a design used by [Renan Rennó](https://www.linkedin.com/in/renanrenno/) when we worked together. Special thanks to everyone who I made use this architecture through the years and to [Sylvain Cornillon](https://www.bossastudios.com/the-team/), who allowed me to open source it and helped me find more proper names for the classes, or I'd be still calling it *"UIManagerOrWhatevs"* cause I'm a rebel. 76 | 77 | You can read my original (even more) verbose post about this architecture [in my blog](http://yankooliveira.com/index.php/2017/12/27/uisystem/) and poke me on twitter [@yankooliveira](https://twitter.com/yankooliveira). 78 | -------------------------------------------------------------------------------- /ScreenTransitions/ATransitionComponent.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | 4 | namespace deVoid.UIFramework { 5 | /// 6 | /// Screens use ATransitionComponents to animate their in and out transitions. 7 | /// This can be extended to use Lerps, animations etc. 8 | /// 9 | public abstract class ATransitionComponent : MonoBehaviour { 10 | /// 11 | /// Animate the specified target transform and execute CallWhenFinished when the animation is done. 12 | /// 13 | /// Target transform. 14 | /// Delegate to be called when animation is finished. 15 | public abstract void Animate(Transform target, Action callWhenFinished); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ScreenTransitions/LegacyAnimationScreenTransition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | namespace deVoid.UIFramework.Examples 6 | { 7 | /// 8 | /// I have avoided using the Legacy Animation system for ages, but since I know people 9 | /// will want to have hand-authored animations on their UI and I highly recommend 10 | /// *not* using Animator for that, both for workflow and performance reasons 11 | /// (ref: https://www.youtube.com/watch?v=_wxitgdx-UI&t=2883s ), 12 | /// I decided to add this example using the Legacy system. An alternative you can 13 | /// look into is the SimpleAnimationComponent 14 | /// (ref: https://blogs.unity3d.com/2017/11/28/introducing-the-simple-animation-component/ ) 15 | /// Although it still runs on top of Animator, at least it might have a simpler workflow. 16 | /// 17 | /// Word of warning: this seems to work, but was barely tested. Be careful if taking it into 18 | /// production :D 19 | /// 20 | public class LegacyAnimationScreenTransition : ATransitionComponent 21 | { 22 | [SerializeField] private AnimationClip clip = null; 23 | [SerializeField] private bool playReverse = false; 24 | 25 | private Action previousCallbackWhenFinished; 26 | 27 | public override void Animate(Transform target, Action callWhenFinished) { 28 | FinishPrevious(); 29 | var targetAnimation = target.GetComponent(); 30 | if (targetAnimation == null) { 31 | Debug.LogError("[LegacyAnimationScreenTransition] No Animation component in " + target); 32 | if (callWhenFinished != null) { 33 | callWhenFinished(); 34 | } 35 | 36 | return; 37 | } 38 | 39 | targetAnimation.clip = clip; 40 | StartCoroutine(PlayAnimationRoutine(targetAnimation, callWhenFinished)); 41 | } 42 | 43 | private IEnumerator PlayAnimationRoutine(Animation targetAnimation, Action callWhenFinished) { 44 | previousCallbackWhenFinished = callWhenFinished; 45 | foreach (AnimationState state in targetAnimation) { 46 | state.time = playReverse ? state.clip.length : 0f; 47 | state.speed = playReverse ? -1f : 1f; 48 | } 49 | 50 | targetAnimation.Play(PlayMode.StopAll); 51 | yield return new WaitForSeconds(targetAnimation.clip.length); 52 | FinishPrevious(); 53 | } 54 | 55 | private void FinishPrevious() { 56 | if (previousCallbackWhenFinished != null) { 57 | previousCallbackWhenFinished(); 58 | previousCallbackWhenFinished = null; 59 | } 60 | 61 | StopAllCoroutines(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ScreenTransitions/SimpleFadeTransition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace deVoid.UIFramework 5 | { 6 | /// 7 | /// This is a simple fade transition implemented as a built-in example. 8 | /// I recommend using a free tweening library like DOTween (http://dotween.demigiant.com/) 9 | /// or rolling out your own. 10 | /// Check the Examples project for more robust and battle-tested options: 11 | /// https://github.com/yankooliveira/uiframework_examples 12 | /// 13 | public class SimpleFadeTransition : ATransitionComponent 14 | { 15 | [SerializeField] private float fadeDuration = 0.5f; 16 | [SerializeField] private bool fadeOut = false; 17 | 18 | private CanvasGroup canvasGroup; 19 | private float timer; 20 | private Action currentAction; 21 | private Transform currentTarget; 22 | 23 | private float startValue; 24 | private float endValue; 25 | 26 | private bool shouldAnimate; 27 | 28 | public override void Animate(Transform target, Action callWhenFinished) { 29 | if (currentAction != null) { 30 | canvasGroup.alpha = endValue; 31 | currentAction(); 32 | } 33 | 34 | canvasGroup = target.GetComponent(); 35 | if (canvasGroup == null) { 36 | canvasGroup = target.gameObject.AddComponent(); 37 | } 38 | 39 | if (fadeOut) { 40 | startValue = 1f; 41 | endValue = 0f; 42 | } 43 | else { 44 | startValue = 0f; 45 | endValue = 1f; 46 | } 47 | 48 | currentAction = callWhenFinished; 49 | timer = fadeDuration; 50 | 51 | canvasGroup.alpha = startValue; 52 | shouldAnimate = true; 53 | } 54 | 55 | private void Update() { 56 | if (!shouldAnimate) { 57 | return; 58 | } 59 | 60 | if (timer > 0f) { 61 | timer -= Time.deltaTime; 62 | canvasGroup.alpha = Mathf.Lerp(endValue, startValue, timer / fadeDuration); 63 | } 64 | else { 65 | canvasGroup.alpha = 1f; 66 | if (currentAction != null) { 67 | currentAction(); 68 | } 69 | 70 | currentAction = null; 71 | shouldAnimate = false; 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /UIFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.UI; 4 | 5 | namespace deVoid.UIFramework 6 | { 7 | /// 8 | /// This is the centralized access point for all things UI. 9 | /// All your calls should be directed at this. 10 | /// 11 | public class UIFrame : MonoBehaviour 12 | { 13 | [Tooltip("Set this to false if you want to manually initialize this UI Frame.")] 14 | [SerializeField] private bool initializeOnAwake = true; 15 | 16 | private PanelUILayer panelLayer; 17 | private WindowUILayer windowLayer; 18 | 19 | private Canvas mainCanvas; 20 | private GraphicRaycaster graphicRaycaster; 21 | 22 | /// 23 | /// The main canvas of this UI 24 | /// 25 | public Canvas MainCanvas { 26 | get { 27 | if (mainCanvas == null) { 28 | mainCanvas = GetComponent(); 29 | } 30 | 31 | return mainCanvas; 32 | } 33 | } 34 | 35 | /// 36 | /// The Camera being used by the Main UI Canvas 37 | /// 38 | public Camera UICamera { 39 | get { return MainCanvas.worldCamera; } 40 | } 41 | 42 | private void Awake() { 43 | if (initializeOnAwake) { 44 | Initialize(); 45 | } 46 | } 47 | 48 | /// 49 | /// Initializes this UI Frame. Initialization consists of initializing both the Panel and Window layers. 50 | /// Although literally all the cases I've had to this day were covered by the "Window and Panel" approach, 51 | /// I made it virtual in case you ever need additional layers or other special initialization. 52 | /// 53 | public virtual void Initialize() { 54 | if (panelLayer == null) { 55 | panelLayer = gameObject.GetComponentInChildren(true); 56 | if (panelLayer == null) { 57 | Debug.LogError("[UI Frame] UI Frame lacks Panel Layer!"); 58 | } 59 | else { 60 | panelLayer.Initialize(); 61 | } 62 | } 63 | 64 | if (windowLayer == null) { 65 | windowLayer = gameObject.GetComponentInChildren(true); 66 | if (panelLayer == null) { 67 | Debug.LogError("[UI Frame] UI Frame lacks Window Layer!"); 68 | } 69 | else { 70 | windowLayer.Initialize(); 71 | windowLayer.RequestScreenBlock += OnRequestScreenBlock; 72 | windowLayer.RequestScreenUnblock += OnRequestScreenUnblock; 73 | } 74 | } 75 | 76 | graphicRaycaster = MainCanvas.GetComponent(); 77 | } 78 | 79 | /// 80 | /// Shows a panel by its id, passing no Properties. 81 | /// 82 | /// Panel Id 83 | public void ShowPanel(string screenId) { 84 | panelLayer.ShowScreenById(screenId); 85 | } 86 | 87 | /// 88 | /// Shows a panel by its id, passing parameters. 89 | /// 90 | /// Identifier. 91 | /// Properties. 92 | /// The type of properties to be passed in. 93 | /// 94 | public void ShowPanel(string screenId, T properties) where T : IPanelProperties { 95 | panelLayer.ShowScreenById(screenId, properties); 96 | } 97 | 98 | /// 99 | /// Hides the panel with the given id. 100 | /// 101 | /// Identifier. 102 | public void HidePanel(string screenId) { 103 | panelLayer.HideScreenById(screenId); 104 | } 105 | 106 | /// 107 | /// Opens the Window with the given Id, with no Properties. 108 | /// 109 | /// Identifier. 110 | public void OpenWindow(string screenId) { 111 | windowLayer.ShowScreenById(screenId); 112 | } 113 | 114 | /// 115 | /// Closes the Window with the given Id. 116 | /// 117 | /// Identifier. 118 | public void CloseWindow(string screenId) { 119 | windowLayer.HideScreenById(screenId); 120 | } 121 | 122 | /// 123 | /// Closes the currently open window, if any is open 124 | /// 125 | public void CloseCurrentWindow() { 126 | if (windowLayer.CurrentWindow != null) { 127 | CloseWindow(windowLayer.CurrentWindow.ScreenId); 128 | } 129 | } 130 | 131 | /// 132 | /// Opens the Window with the given id, passing in Properties. 133 | /// 134 | /// Identifier. 135 | /// Properties. 136 | /// The type of properties to be passed in. 137 | /// 138 | public void OpenWindow(string screenId, T properties) where T : IWindowProperties { 139 | windowLayer.ShowScreenById(screenId, properties); 140 | } 141 | 142 | /// 143 | /// Searches for the given id among the Layers, opens the Screen if it finds it 144 | /// 145 | /// The Screen id. 146 | public void ShowScreen(string screenId) { 147 | Type type; 148 | if (IsScreenRegistered(screenId, out type)) { 149 | if (type == typeof(IWindowController)) { 150 | OpenWindow(screenId); 151 | } 152 | else if (type == typeof(IPanelController)) { 153 | ShowPanel(screenId); 154 | } 155 | } 156 | else { 157 | Debug.LogError(string.Format("Tried to open Screen id {0} but it's not registered as Window or Panel!", 158 | screenId)); 159 | } 160 | } 161 | 162 | /// 163 | /// Registers a screen. If transform is passed, the layer will 164 | /// reparent it to itself. Screens can only be shown after they're registered. 165 | /// 166 | /// Screen identifier. 167 | /// Controller. 168 | /// Screen transform. If not null, will be reparented to proper layer 169 | public void RegisterScreen(string screenId, IUIScreenController controller, Transform screenTransform) { 170 | IWindowController window = controller as IWindowController; 171 | if (window != null) { 172 | windowLayer.RegisterScreen(screenId, window); 173 | if (screenTransform != null) { 174 | windowLayer.ReparentScreen(controller, screenTransform); 175 | } 176 | 177 | return; 178 | } 179 | 180 | IPanelController panel = controller as IPanelController; 181 | if (panel != null) { 182 | panelLayer.RegisterScreen(screenId, panel); 183 | if (screenTransform != null) { 184 | panelLayer.ReparentScreen(controller, screenTransform); 185 | } 186 | } 187 | } 188 | 189 | /// 190 | /// Registers the panel. Panels can only be shown after they're registered. 191 | /// 192 | /// Screen identifier. 193 | /// Controller. 194 | /// The Controller type. 195 | public void RegisterPanel(string screenId, TPanel controller) where TPanel : IPanelController { 196 | panelLayer.RegisterScreen(screenId, controller); 197 | } 198 | 199 | /// 200 | /// Unregisters the panel. 201 | /// 202 | /// Screen identifier. 203 | /// Controller. 204 | /// The Controller type. 205 | public void UnregisterPanel(string screenId, TPanel controller) where TPanel : IPanelController { 206 | panelLayer.UnregisterScreen(screenId, controller); 207 | } 208 | 209 | /// 210 | /// Registers the Window. Windows can only be opened after they're registered. 211 | /// 212 | /// Screen identifier. 213 | /// Controller. 214 | /// The Controller type. 215 | public void RegisterWindow(string screenId, TWindow controller) where TWindow : IWindowController { 216 | windowLayer.RegisterScreen(screenId, controller); 217 | } 218 | 219 | /// 220 | /// Unregisters the Window. 221 | /// 222 | /// Screen identifier. 223 | /// Controller. 224 | /// The Controller type. 225 | public void UnregisterWindow(string screenId, TWindow controller) where TWindow : IWindowController { 226 | windowLayer.UnregisterScreen(screenId, controller); 227 | } 228 | 229 | /// 230 | /// Checks if a given Panel is open. 231 | /// 232 | /// Panel identifier. 233 | public bool IsPanelOpen(string panelId) { 234 | return panelLayer.IsPanelVisible(panelId); 235 | } 236 | 237 | /// 238 | /// Hide all screens 239 | /// 240 | /// Defines if screens should the screens animate out or not. 241 | public void HideAll(bool animate = true) { 242 | CloseAllWindows(animate); 243 | HideAllPanels(animate); 244 | } 245 | 246 | /// 247 | /// Hide all screens on the Panel Layer 248 | /// 249 | /// Defines if screens should the screens animate out or not. 250 | public void HideAllPanels(bool animate = true) { 251 | panelLayer.HideAll(animate); 252 | } 253 | 254 | /// 255 | /// Hide all screens in the Window Layer 256 | /// 257 | /// Defines if screens should the screens animate out or not. 258 | public void CloseAllWindows(bool animate = true) { 259 | windowLayer.HideAll(animate); 260 | } 261 | 262 | /// 263 | /// Checks if a given screen id is registered to either the Window or Panel layers 264 | /// 265 | /// The Id to check. 266 | public bool IsScreenRegistered(string screenId) { 267 | if (windowLayer.IsScreenRegistered(screenId)) { 268 | return true; 269 | } 270 | 271 | if (panelLayer.IsScreenRegistered(screenId)) { 272 | return true; 273 | } 274 | 275 | return false; 276 | } 277 | 278 | /// 279 | /// Checks if a given screen id is registered to either the Window or Panel layers, 280 | /// also returning the screen type 281 | /// 282 | /// The Id to check. 283 | /// The type of the screen. 284 | public bool IsScreenRegistered(string screenId, out Type type) { 285 | if (windowLayer.IsScreenRegistered(screenId)) { 286 | type = typeof(IWindowController); 287 | return true; 288 | } 289 | 290 | if (panelLayer.IsScreenRegistered(screenId)) { 291 | type = typeof(IPanelController); 292 | return true; 293 | } 294 | 295 | type = null; 296 | return false; 297 | } 298 | 299 | private void OnRequestScreenBlock() { 300 | if (graphicRaycaster != null) { 301 | graphicRaycaster.enabled = false; 302 | } 303 | } 304 | 305 | private void OnRequestScreenUnblock() { 306 | if (graphicRaycaster != null) { 307 | graphicRaycaster.enabled = true; 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /UISettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace deVoid.UIFramework 5 | { 6 | /// 7 | /// Template for an UI. You can rig the prefab for the UI Frame itself and all the screens that should 8 | /// be instanced and registered upon instantiating a new UI Frame. 9 | /// 10 | 11 | [CreateAssetMenu(fileName = "UISettings", menuName = "deVoid UI/UI Settings")] 12 | public class UISettings : ScriptableObject 13 | { 14 | [Tooltip("Prefab for the UI Frame structure itself")] 15 | [SerializeField] private UIFrame templateUIPrefab = null; 16 | [Tooltip("Prefabs for all the screens (both Panels and Windows) that are to be instanced and registered when the UI is instantiated")] 17 | [SerializeField] private List screensToRegister = null; 18 | [Tooltip("In case a screen prefab is not deactivated, should the system automatically deactivate its GameObject upon instantiation? If false, the screen will be at a visible state upon instantiation.")] 19 | [SerializeField] private bool deactivateScreenGOs = true; 20 | 21 | /// 22 | /// Creates an instance of the UI Frame Prefab. By default, also instantiates 23 | /// all the screens listed and registers them. If the deactivateScreenGOs flag is 24 | /// true, it will deactivate all Screen GameObjects in case they're active. 25 | /// 26 | /// Should the screens listed in the Settings file be instanced and registered? 27 | /// A new UI Frame 28 | public UIFrame CreateUIInstance(bool instanceAndRegisterScreens = true) { 29 | var newUI = Instantiate(templateUIPrefab); 30 | 31 | if (instanceAndRegisterScreens) { 32 | foreach (var screen in screensToRegister) { 33 | var screenInstance = Instantiate(screen); 34 | var screenController = screenInstance.GetComponent(); 35 | 36 | if (screenController != null) { 37 | newUI.RegisterScreen(screen.name, screenController, screenInstance.transform); 38 | if (deactivateScreenGOs && screenInstance.activeSelf) { 39 | screenInstance.SetActive(false); 40 | } 41 | } 42 | else { 43 | Debug.LogError("[UIConfig] Screen doesn't contain a ScreenController! Skipping " + screen.name); 44 | } 45 | } 46 | } 47 | 48 | return newUI; 49 | } 50 | 51 | private void OnValidate() { 52 | List objectsToRemove = new List(); 53 | for(int i = 0; i < screensToRegister.Count; i++) { 54 | var screenCtl = screensToRegister[i].GetComponent(); 55 | if (screenCtl == null) { 56 | objectsToRemove.Add(screensToRegister[i]); 57 | } 58 | } 59 | 60 | if (objectsToRemove.Count > 0) { 61 | Debug.LogError("[UISettings] Some GameObjects that were added to the Screen Prefab List didn't have ScreenControllers attached to them! Removing."); 62 | foreach (var obj in objectsToRemove) { 63 | Debug.LogError("[UISettings] Removed " + obj.name + " from " + name + " as it has no Screen Controller attached!"); 64 | screensToRegister.Remove(obj); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Window/AWindowController.cs: -------------------------------------------------------------------------------- 1 | namespace deVoid.UIFramework 2 | { 3 | /// 4 | /// Base implementation for Window ScreenControllers that need no special Properties 5 | /// 6 | public abstract class AWindowController : AWindowController { } 7 | 8 | /// 9 | /// Base implementation for Window ScreenControllers. Its parameter is a specific type of IWindowProperties. 10 | /// In case your window doesn't need special properties, inherit from AWindowScreenController, without Generic param. 11 | /// 12 | /// 13 | /// 14 | public abstract class AWindowController : AUIScreenController, IWindowController 15 | where TProps : IWindowProperties 16 | { 17 | public bool HideOnForegroundLost { 18 | get { return Properties.HideOnForegroundLost; } 19 | } 20 | 21 | public bool IsPopup { 22 | get { return Properties.IsPopup; } 23 | } 24 | 25 | public WindowPriority WindowPriority { 26 | get { return Properties.WindowQueuePriority; } 27 | } 28 | 29 | /// 30 | /// Requests this Window to be closed, handy for rigging it directly in the Editor. 31 | /// I use the UI_ prefix to group all the methods that should be rigged in the Editor so that it's 32 | /// easy to find the screen-specific methods. It breaks naming convention, but does more good than harm as 33 | /// the amount of methods grow. 34 | /// This is *not* called every time it is closed, just upon user input - for that behaviour, see 35 | /// WhileHiding(); 36 | /// 37 | public virtual void UI_Close() { 38 | CloseRequest(this); 39 | } 40 | 41 | protected sealed override void SetProperties(TProps props) { 42 | if (props != null) { 43 | // If the Properties set on the prefab should not be overwritten, 44 | // copy the default values to the passed in properties 45 | if (!props.SuppressPrefabProperties) { 46 | props.HideOnForegroundLost = Properties.HideOnForegroundLost; 47 | props.WindowQueuePriority = Properties.WindowQueuePriority; 48 | props.IsPopup = Properties.IsPopup; 49 | } 50 | 51 | Properties = props; 52 | } 53 | } 54 | 55 | protected override void HierarchyFixOnShow() { 56 | transform.SetAsLastSibling(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Window/WindowHistoryEntry.cs: -------------------------------------------------------------------------------- 1 | namespace deVoid.UIFramework { 2 | /// 3 | /// An entry for controlling window history and queue 4 | /// 5 | public struct WindowHistoryEntry 6 | { 7 | public readonly IWindowController Screen; 8 | public readonly IWindowProperties Properties; 9 | 10 | public WindowHistoryEntry(IWindowController screen, IWindowProperties properties) { 11 | Screen = screen; 12 | Properties = properties; 13 | } 14 | 15 | public void Show() { 16 | Screen.Show(Properties); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Window/WindowParaLayer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | namespace deVoid.UIFramework { 5 | /// 6 | /// This is a "helper" layer so Windows with higher priority can be displayed. 7 | /// By default, it contains any window tagged as a Popup. It is controlled by the WindowUILayer. 8 | /// 9 | public class WindowParaLayer : MonoBehaviour { 10 | [SerializeField] 11 | private GameObject darkenBgObject = null; 12 | 13 | private List containedScreens = new List(); 14 | 15 | public void AddScreen(Transform screenRectTransform) { 16 | screenRectTransform.SetParent(transform, false); 17 | containedScreens.Add(screenRectTransform.gameObject); 18 | } 19 | 20 | public void RefreshDarken() { 21 | for (int i = 0; i < containedScreens.Count; i++) { 22 | if (containedScreens[i] != null) { 23 | if (containedScreens[i].activeSelf) { 24 | darkenBgObject.SetActive(true); 25 | return; 26 | } 27 | } 28 | } 29 | 30 | darkenBgObject.SetActive(false); 31 | } 32 | 33 | public void DarkenBG() { 34 | darkenBgObject.SetActive(true); 35 | darkenBgObject.transform.SetAsLastSibling(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Window/WindowPriority.cs: -------------------------------------------------------------------------------- 1 | namespace deVoid.UIFramework { 2 | /// 3 | /// Enum to define behaviour of Windows 4 | /// upon opening, in the history and queue 5 | /// 6 | public enum WindowPriority { 7 | ForceForeground = 0, 8 | Enqueue = 1, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Window/WindowProperties.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace deVoid.UIFramework { 4 | /// 5 | /// Properties common to all windows 6 | /// 7 | [System.Serializable] 8 | public class WindowProperties : IWindowProperties { 9 | [SerializeField] 10 | protected bool hideOnForegroundLost = true; 11 | 12 | [SerializeField] 13 | protected WindowPriority windowQueuePriority = WindowPriority.ForceForeground; 14 | 15 | [SerializeField] 16 | protected bool isPopup = false; 17 | 18 | public WindowProperties() { 19 | hideOnForegroundLost = true; 20 | windowQueuePriority = WindowPriority.ForceForeground; 21 | isPopup = false; 22 | } 23 | 24 | /// 25 | /// How should this window behave in case another window 26 | /// is already opened? 27 | /// 28 | /// Force Foreground opens it immediately, Enqueue queues it so that it's opened as soon as 29 | /// the current one is closed. 30 | public WindowPriority WindowQueuePriority { 31 | get { return windowQueuePriority; } 32 | set { windowQueuePriority = value; } 33 | } 34 | 35 | /// 36 | /// Should this window be hidden when other window takes its foreground? 37 | /// 38 | /// true if hide on foreground lost; otherwise, false. 39 | public bool HideOnForegroundLost { 40 | get { return hideOnForegroundLost; } 41 | set { hideOnForegroundLost = value; } 42 | } 43 | 44 | /// 45 | /// When properties are passed in the Open() call, should the ones 46 | /// configured in the viewPrefab be overwritten? 47 | /// 48 | /// true if suppress viewPrefab properties; otherwise, false. 49 | public bool SuppressPrefabProperties { get; set; } 50 | 51 | /// 52 | /// Popups are displayed with a black background behind them and 53 | /// in front of all other Windows 54 | /// 55 | /// true if this window is a popup; otherwise, false. 56 | public bool IsPopup { 57 | get { return isPopup; } 58 | set { isPopup = value; } 59 | } 60 | 61 | public WindowProperties(bool suppressPrefabProperties = false) { 62 | WindowQueuePriority = WindowPriority.ForceForeground; 63 | HideOnForegroundLost = false; 64 | SuppressPrefabProperties = suppressPrefabProperties; 65 | } 66 | 67 | public WindowProperties(WindowPriority priority, bool hideOnForegroundLost = false, bool suppressPrefabProperties = false) { 68 | WindowQueuePriority = priority; 69 | HideOnForegroundLost = hideOnForegroundLost; 70 | SuppressPrefabProperties = suppressPrefabProperties; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Window/WindowUILayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace deVoid.UIFramework 6 | { 7 | /// 8 | /// This layer controls all Windows. 9 | /// Windows are Screens that follow a history and a queue, and are displayed 10 | /// one at a time (and may or may not be modals). This also includes pop-ups. 11 | /// 12 | public class WindowUILayer : AUILayer 13 | { 14 | [SerializeField] private WindowParaLayer priorityParaLayer = null; 15 | 16 | public IWindowController CurrentWindow { get; private set; } 17 | 18 | private Queue windowQueue; 19 | private Stack windowHistory; 20 | 21 | public event Action RequestScreenBlock; 22 | public event Action RequestScreenUnblock; 23 | 24 | private bool IsScreenTransitionInProgress { 25 | get { return screensTransitioning.Count != 0; } 26 | } 27 | 28 | private HashSet screensTransitioning; 29 | 30 | public override void Initialize() { 31 | base.Initialize(); 32 | registeredScreens = new Dictionary(); 33 | windowQueue = new Queue(); 34 | windowHistory = new Stack(); 35 | screensTransitioning = new HashSet(); 36 | } 37 | 38 | protected override void ProcessScreenRegister(string screenId, IWindowController controller) { 39 | base.ProcessScreenRegister(screenId, controller); 40 | controller.InTransitionFinished += OnInAnimationFinished; 41 | controller.OutTransitionFinished += OnOutAnimationFinished; 42 | controller.CloseRequest += OnCloseRequestedByWindow; 43 | } 44 | 45 | protected override void ProcessScreenUnregister(string screenId, IWindowController controller) { 46 | base.ProcessScreenUnregister(screenId, controller); 47 | controller.InTransitionFinished -= OnInAnimationFinished; 48 | controller.OutTransitionFinished -= OnOutAnimationFinished; 49 | controller.CloseRequest -= OnCloseRequestedByWindow; 50 | } 51 | 52 | public override void ShowScreen(IWindowController screen) { 53 | ShowScreen(screen, null); 54 | } 55 | 56 | public override void ShowScreen(IWindowController screen, TProp properties) { 57 | IWindowProperties windowProp = properties as IWindowProperties; 58 | 59 | if (ShouldEnqueue(screen, windowProp)) { 60 | EnqueueWindow(screen, properties); 61 | } 62 | else { 63 | DoShow(screen, windowProp); 64 | } 65 | } 66 | 67 | public override void HideScreen(IWindowController screen) { 68 | if (screen == CurrentWindow) { 69 | windowHistory.Pop(); 70 | AddTransition(screen); 71 | screen.Hide(); 72 | 73 | CurrentWindow = null; 74 | 75 | if (windowQueue.Count > 0) { 76 | ShowNextInQueue(); 77 | } 78 | else if (windowHistory.Count > 0) { 79 | ShowPreviousInHistory(); 80 | } 81 | } 82 | else { 83 | Debug.LogError( 84 | string.Format( 85 | "[WindowUILayer] Hide requested on WindowId {0} but that's not the currently open one ({1})! Ignoring request.", 86 | screen.ScreenId, CurrentWindow != null ? CurrentWindow.ScreenId : "current is null")); 87 | } 88 | } 89 | 90 | public override void HideAll(bool shouldAnimateWhenHiding = true) { 91 | base.HideAll(shouldAnimateWhenHiding); 92 | CurrentWindow = null; 93 | priorityParaLayer.RefreshDarken(); 94 | windowHistory.Clear(); 95 | } 96 | 97 | public override void ReparentScreen(IUIScreenController controller, Transform screenTransform) { 98 | IWindowController window = controller as IWindowController; 99 | 100 | if (window == null) { 101 | Debug.LogError("[WindowUILayer] Screen " + screenTransform.name + " is not a Window!"); 102 | } 103 | else { 104 | if (window.IsPopup) { 105 | priorityParaLayer.AddScreen(screenTransform); 106 | return; 107 | } 108 | } 109 | 110 | base.ReparentScreen(controller, screenTransform); 111 | } 112 | 113 | private void EnqueueWindow(IWindowController screen, TProp properties) where TProp : IScreenProperties { 114 | windowQueue.Enqueue(new WindowHistoryEntry(screen, (IWindowProperties) properties)); 115 | } 116 | 117 | private bool ShouldEnqueue(IWindowController controller, IWindowProperties windowProp) { 118 | if (CurrentWindow == null && windowQueue.Count == 0) { 119 | return false; 120 | } 121 | 122 | if (windowProp != null && windowProp.SuppressPrefabProperties) { 123 | return windowProp.WindowQueuePriority != WindowPriority.ForceForeground; 124 | } 125 | 126 | if (controller.WindowPriority != WindowPriority.ForceForeground) { 127 | return true; 128 | } 129 | 130 | return false; 131 | } 132 | 133 | private void ShowPreviousInHistory() { 134 | if (windowHistory.Count > 0) { 135 | WindowHistoryEntry window = windowHistory.Pop(); 136 | DoShow(window); 137 | } 138 | } 139 | 140 | private void ShowNextInQueue() { 141 | if (windowQueue.Count > 0) { 142 | WindowHistoryEntry window = windowQueue.Dequeue(); 143 | DoShow(window); 144 | } 145 | } 146 | 147 | private void DoShow(IWindowController screen, IWindowProperties properties) { 148 | DoShow(new WindowHistoryEntry(screen, properties)); 149 | } 150 | 151 | private void DoShow(WindowHistoryEntry windowEntry) { 152 | if (CurrentWindow == windowEntry.Screen) { 153 | Debug.LogWarning( 154 | string.Format( 155 | "[WindowUILayer] The requested WindowId ({0}) is already open! This will add a duplicate to the " + 156 | "history and might cause inconsistent behaviour. It is recommended that if you need to open the same" + 157 | "screen multiple times (eg: when implementing a warning message pop-up), it closes itself upon the player input" + 158 | "that triggers the continuation of the flow." 159 | , CurrentWindow.ScreenId)); 160 | } 161 | else if (CurrentWindow != null 162 | && CurrentWindow.HideOnForegroundLost 163 | && !windowEntry.Screen.IsPopup) { 164 | CurrentWindow.Hide(); 165 | } 166 | 167 | windowHistory.Push(windowEntry); 168 | AddTransition(windowEntry.Screen); 169 | 170 | if (windowEntry.Screen.IsPopup) { 171 | priorityParaLayer.DarkenBG(); 172 | } 173 | 174 | windowEntry.Show(); 175 | 176 | CurrentWindow = windowEntry.Screen; 177 | } 178 | 179 | private void OnInAnimationFinished(IUIScreenController screen) { 180 | RemoveTransition(screen); 181 | } 182 | 183 | private void OnOutAnimationFinished(IUIScreenController screen) { 184 | RemoveTransition(screen); 185 | var window = screen as IWindowController; 186 | if (window.IsPopup) { 187 | priorityParaLayer.RefreshDarken(); 188 | } 189 | } 190 | 191 | private void OnCloseRequestedByWindow(IUIScreenController screen) { 192 | HideScreen(screen as IWindowController); 193 | } 194 | 195 | private void AddTransition(IUIScreenController screen) { 196 | screensTransitioning.Add(screen); 197 | if (RequestScreenBlock != null) { 198 | RequestScreenBlock(); 199 | } 200 | } 201 | 202 | private void RemoveTransition(IUIScreenController screen) { 203 | screensTransitioning.Remove(screen); 204 | if (!IsScreenTransitionInProgress) { 205 | if (RequestScreenUnblock != null) { 206 | RequestScreenUnblock(); 207 | } 208 | } 209 | } 210 | } 211 | } 212 | --------------------------------------------------------------------------------