Skip to content

Instantly share code, notes, and snippets.

@LordNed
Created November 10, 2015 04:59
Show Gist options
  • Save LordNed/454d4e3f0104e179d400 to your computer and use it in GitHub Desktop.
Save LordNed/454d4e3f0104e179d400 to your computer and use it in GitHub Desktop.
Simple UI panel manager for use in bringing a more state-like approach to the UI. Only one panel can be active at a time, but multiple overlays can be active. To use, derive a class from either UIBasePanel, or UIBaseOverlay and put them on an object in the scene. Also put an instance of UIPanelManager, preferably on the root of your UI.
using UnityEngine;
using System.Collections;
public class UIBaseOverlay : MonoBehaviour
{
public virtual IEnumerator OnOverlayActivated()
{
yield break;
}
public virtual IEnumerator OnOverlayDeactivated()
{
yield break;
}
}
using UnityEngine;
using System.Collections;
public class UIBasePanel : MonoBehaviour
{
public virtual IEnumerator OnPanelActivated()
{
yield break;
}
public virtual IEnumerator OnPanelDeactivated()
{
yield break;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIPanelManager : MonoBehaviour
{
private List<UIBasePanel> m_cachedPanelList;
private List<UIBaseOverlay> m_cachedOverlayList;
private Stack<UIBasePanel> m_panelStack;
private List<UIBaseOverlay> m_overlayList;
private bool m_isTransitioning;
public static UIPanelManager Instance;
void Awake()
{
m_panelStack = new Stack<UIBasePanel>();
m_overlayList = new List<UIBaseOverlay>();
m_cachedPanelList = new List<UIBasePanel>();
m_cachedOverlayList = new List<UIBaseOverlay>();
Instance = this;
UpdateTrackedChildren();
}
private void OnLevelWasLoaded(int levelIndex)
{
Debug.Log("[LuxelOperation.UIController] OnLevelWasLoaded triggered, updating tracked children.");
UpdateTrackedChildren();
}
public void UpdateTrackedChildren()
{
// Clear our list of previously tracked children (panels and overlays)
m_cachedPanelList.Clear();
m_cachedOverlayList.Clear();
// Find all UIControllerRoots in the scene.
Canvas[] roots = FindObjectsOfType<Canvas>();
foreach (var root in roots)
{
m_cachedPanelList.AddRange(root.GetComponentsInChildren<UIBasePanel>(true));
m_cachedOverlayList.AddRange(root.GetComponentsInChildren<UIBaseOverlay>(true));
}
// Ensure all panels start disabled so it doesn't matter what the state of the panels is (during dev).
foreach (var panel in m_cachedPanelList)
panel.gameObject.SetActive(false);
foreach (var overlay in m_cachedOverlayList)
overlay.gameObject.SetActive(false);
}
#region Panels
private IEnumerator PopPanelCoroutine(Action onComplete = null)
{
m_isTransitioning = true;
// Deactivate the top panel, pop it, then activate the new top (again, due to it transitioning out before).
UIBasePanel topPanel = m_panelStack.Peek();
yield return StartCoroutine(DeactivatePanelCoroutine(topPanel));
// Update stack state to match...
m_panelStack.Pop();
if (m_panelStack.Count > 0)
{
yield return StartCoroutine(ActivatePanelCoroutine(m_panelStack.Peek()));
}
if (onComplete != null)
onComplete();
m_isTransitioning = false;
}
private IEnumerator PushPanelCoroutine<T>(Action onComplete = null) where T : UIBasePanel
{
m_isTransitioning = true;
// Deactivate the top panel, wait for it to finish, then push our own.
if (m_panelStack.Count > 0)
yield return StartCoroutine(DeactivatePanelCoroutine(m_panelStack.Peek()));
// Find the new panel that we want to push to the top.
foreach (var panel in m_cachedPanelList)
{
if (panel.GetType() == typeof(T))
{
m_panelStack.Push(panel);
yield return StartCoroutine(ActivatePanelCoroutine(panel));
m_isTransitioning = false;
if (onComplete != null)
onComplete();
// Leave the function early, we're done here.
yield break;
}
}
// If we didn't find the panel, ensure we break the transition lock
// and warn user.
m_isTransitioning = false;
Debug.LogWarning(string.Format("[Helios.UIPanelManager] Failed to find panel of type {0} in UIPanelManager!", typeof(T)));
if (onComplete != null)
onComplete();
}
private IEnumerator PushPanelCoroutine<T>(bool overrideYield = false, Action onComplete = null) where T : UIBasePanel
{
m_isTransitioning = true;
// Deactivate the top panel, wait for it to finish, then push our own.
if (m_panelStack.Count > 0)
{
if (overrideYield)
{
StartCoroutine(DeactivatePanelCoroutine(m_panelStack.Peek()));
}
else
{
yield return StartCoroutine(DeactivatePanelCoroutine(m_panelStack.Peek()));
}
}
// Find the new panel that we want to push to the top.
foreach (var panel in m_cachedPanelList)
{
if (panel.GetType() == typeof(T))
{
m_panelStack.Push(panel);
yield return StartCoroutine(ActivatePanelCoroutine(panel));
m_isTransitioning = false;
if (onComplete != null)
onComplete();
// Leave the function early, we're done here.
yield break;
}
}
// If we didn't find the panel, ensure we break the transition lock
// and warn user.
m_isTransitioning = false;
Debug.LogWarning(string.Format("[Helios.UIPanelManager] Failed to find panel of type {0} in UIPanelManager!", typeof(T)));
if (onComplete != null)
onComplete();
}
public IEnumerator DeactivatePanelCoroutine(UIBasePanel panel)
{
if (panel.gameObject.activeSelf)
{
yield return StartCoroutine(panel.OnPanelDeactivated());
panel.gameObject.SetActive(false);
}
}
private IEnumerator ActivatePanelCoroutine(UIBasePanel panel)
{
panel.gameObject.SetActive(true);
yield return StartCoroutine(panel.OnPanelActivated());
}
private T GetPanelOfType<T>() where T : UIBasePanel
{
foreach (var panel in m_cachedPanelList)
{
if (panel.GetType() == typeof(T))
return (T)panel;
}
// Silently fail as PushPanel already warns user of failure.
return null;
}
#endregion
#region Overlays
private IEnumerator PopOverlayCoroutine(UIBaseOverlay overlay, Action onComplete = null)
{
if (!m_overlayList.Contains(overlay))
{
Debug.Log(string.Format("[Helios.UIPanelManager] Attempted to pop overlay of type {0} in UIPanelManager and could not find!", overlay.GetType()));
if (onComplete != null)
onComplete();
yield break;
}
// Remove from the list immediately, and then turn it off.
m_overlayList.Remove(overlay);
StartCoroutine(DeactivateOverlayCoroutine(overlay));
if (onComplete != null)
onComplete();
yield break;
}
private IEnumerator PushOverlayCoroutine<T>(Action onComplete = null) where T : UIBaseOverlay
{
// Find the new overlay that we want to enable
foreach (var overlay in m_cachedOverlayList)
{
if (overlay.GetType() == typeof(T))
{
if (!m_overlayList.Contains(overlay))
{
m_overlayList.Add(overlay);
StartCoroutine(ActivateOverlayCoroutine(overlay));
}
if (onComplete != null)
onComplete();
// Leave the function early, we're done here.
yield break;
}
}
// If we didn't find the overlay, warn the user.
Debug.LogWarning(string.Format("[Helios.UIPanelManager] Failed to find overlay of type {0} in UIPanelManager!", typeof(T)));
if (onComplete != null)
onComplete();
}
private IEnumerator DeactivateOverlayCoroutine(UIBaseOverlay overlay)
{
yield return StartCoroutine(overlay.OnOverlayDeactivated());
overlay.gameObject.SetActive(false);
}
private IEnumerator ActivateOverlayCoroutine(UIBaseOverlay overlay)
{
overlay.gameObject.SetActive(true);
yield return StartCoroutine(overlay.OnOverlayActivated());
}
private T GetOverlayOfType<T>() where T : UIBaseOverlay
{
foreach (var panel in m_cachedOverlayList)
{
if (panel.GetType() == typeof(T))
return (T)panel;
}
// Silently fail as PushOverlay already warns user of failure.
return null;
}
#endregion
#region Public API
public T PushPanel<T>(Action onComplete = null) where T : UIBasePanel
{
StartCoroutine(PushPanelCoroutine<T>(onComplete));
// Then return a reference to the panel of that type.
return GetPanelOfType<T>();
}
public T PushPanel<T>(bool overrideYield, Action onComplete = null) where T : UIBasePanel
{
StartCoroutine(PushPanelCoroutine<T>(overrideYield, onComplete));
// Then return a reference to the panel of that type.
return GetPanelOfType<T>();
}
public IEnumerator PushPanelAndWait<T>() where T : UIBasePanel
{
yield return StartCoroutine(PushPanelCoroutine<T>(null));
}
public void PopPanel(Action onComplete = null)
{
StartCoroutine(PopPanelCoroutine(onComplete));
}
public T GetPanel<T>() where T : UIBasePanel
{
return GetPanelOfType<T>();
}
public T PushOverlay<T>(Action onComplete = null) where T : UIBaseOverlay
{
StartCoroutine(PushOverlayCoroutine<T>(onComplete));
// Then return a reference to the panel of that type.
return GetOverlayOfType<T>();
}
public void PopOverlay(UIBaseOverlay overlay, Action onComplete = null)
{
StartCoroutine(PopOverlayCoroutine(overlay, onComplete));
}
public T GetOverlay<T>() where T : UIBaseOverlay
{
return GetOverlayOfType<T>();
}
public IEnumerator PushOverlayAndWait<T>() where T : UIBaseOverlay
{
yield return StartCoroutine(PushOverlayCoroutine<T>(null));
}
public bool IsInTransition()
{
return m_isTransitioning;
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment