Skip to content

Commit

Permalink
feat: BREAKING CHANGE: Nuclear Windows Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
Tr0sT authored and serg.morozov committed Jan 6, 2023
1 parent d17caf9 commit a544129
Show file tree
Hide file tree
Showing 108 changed files with 2,976 additions and 2,398 deletions.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#nullable enable
using System;
using UnityEngine;

namespace Nuclear.WindowsManager
{
public static class StaticWindowsManager
{
private static IWindowsManager? _instance;

public static void Init(WindowsManagerSettings settings)
{
if (_instance != null)
{
Debug.LogError("WindowsManager already initialized");
return;
}

_instance = new WindowsManager(settings);
}

public static Window CreateWindow(string path, Action<Window>? setupWindow = null) =>
_instance != null
? _instance.CreateWindow(path, setupWindow)
: throw new ArgumentException("WindowsManager not initialized");

public static void PrefetchWindow(string path)
{
if (_instance != null)
_instance.PrefetchWindow(path);
else
throw new ArgumentException("WindowsManager not initialized");
}

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void Reset()
{
_instance = null!;
}
}
}

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable
using System;
using UnityEngine;

namespace NuclearBand
{
public class BackButtonEventManager : MonoBehaviour
{
public event Action OnBackButtonPressed = delegate { };

private void Update()
{
if(Input.GetKeyDown(KeyCode.Escape) )
OnBackButtonPressed.Invoke();
}
}
}
112 changes: 112 additions & 0 deletions Assets/com.nuclearband.windowsmanager/Runtime/Implementation/Window.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#nullable enable
using System;
using UnityEngine;

namespace Nuclear.WindowsManager
{
public class Window : MonoBehaviour
{
public event Action<Window> OnShown = delegate { };
public event Action<Window> OnHidden = delegate { };
public event Action<Window> OnStartShow = delegate { };
public event Action<Window> OnStartHide = delegate { };

private enum WindowState
{
Opened, Closed, Opening, Closing
}

public bool WithInputBlockForBackground;
public bool DestroyOnClose;
public bool ProcessBackButton = true;

[SerializeField] private WindowAnimation? _showAnimation;
[SerializeField] private WindowAnimation? _hideAnimation;

private WindowState _windowState = WindowState.Closed;

private GameObject? _inputBlock;
private Action? _customBackButtonAction;
private Action _customDeInitActions = delegate { };

public virtual void Init()
{
if (_showAnimation != null)
_showAnimation.OnEnd += EndShowAnimationCallback;

if (_hideAnimation != null)
_hideAnimation.OnEnd += CloseInternal;
}

public void Show(bool immediate = false)
{
OnStartShow.Invoke(this);
_windowState = WindowState.Opening;
if (_showAnimation != null && !immediate)
_showAnimation.Play();
else
EndShowAnimationCallback();
}

public void Close()
{
_windowState = WindowState.Closing;
OnStartHide.Invoke(this);
if (_hideAnimation != null)
_hideAnimation.Play();
else
CloseInternal();
}

public void ProcessBackButtonPress()
{
if (_windowState == WindowState.Closing)
return;

if (_customBackButtonAction != null)
_customBackButtonAction.Invoke();
else
Close();
}

protected void SetCustomBackButtonAction(Action? action) =>
_customBackButtonAction = action;

public void AddDeInitAction(Action action) =>
_customDeInitActions += action;

private void EndShowAnimationCallback()
{
OnShown.Invoke(this);
_windowState = WindowState.Opened;
}

private void CloseInternal()
{
_windowState = WindowState.Closed;
var onHidden = OnHidden;
DeInit();
if (DestroyOnClose)
Destroy(gameObject);
else
gameObject.SetActive(false);
onHidden(this);
}

private void DeInit()
{
OnStartShow = delegate { };
OnShown = delegate { };
OnStartHide = delegate { };
OnHidden = delegate { };
if (_showAnimation != null)
_showAnimation.OnEnd -= EndShowAnimationCallback;

if (_hideAnimation != null)
_hideAnimation.OnEnd -= CloseInternal;

_customDeInitActions.Invoke();
_customDeInitActions = delegate { };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using UnityEngine;

namespace Nuclear.WindowsManager
{
public abstract class WindowAnimation : MonoBehaviour
{
public event Action OnEnd = delegate { };

public abstract void Play();

public void End() => OnEnd.Invoke();
}
}

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#nullable enable
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Collections.ObjectModel;
using NuclearBand;
using Object = UnityEngine.Object;

namespace Nuclear.WindowsManager
{
public class WindowsManager : IWindowsManager
{
public event Action<Window> OnWindowCreated = delegate { };
public event Action<Window> OnWindowClosed = delegate { };

private readonly ReadOnlyDictionary<string, Func<bool>> _suffixesWithPredicates;
private readonly Transform _root ;
private readonly GameObject _inputBlockPrefab;

private readonly List<Window> _windows = new();
private readonly Dictionary<string, GameObject> _loadedWindowPrefabs = new();
private readonly Dictionary<string, List<Window>> _invisibleWindows = new();

public WindowsManager(WindowsManagerSettings settings)
{
_suffixesWithPredicates = settings.SuffixesWithPredicates;

_inputBlockPrefab = Resources.Load<GameObject>(settings.InputBlockPath) ??
throw new ArgumentException("WindowsManager: Wrong path to InputBlock");

var rootPrefab = Resources.Load<GameObject>(settings.RootPath) ??
throw new ArgumentException("WindowsManager: Wrong path to root");

_root = Object.Instantiate(rootPrefab).transform;
_root.name = _root.name.Replace("(Clone)", string.Empty);
var backButtonEventManager = _root.gameObject.AddComponent<BackButtonEventManager>();
backButtonEventManager.OnBackButtonPressed += OnBackButtonPressedCallback;
Object.DontDestroyOnLoad(_root.gameObject);
}

Window IWindowsManager.CreateWindow(string path, Action<Window>? setupWindow)
{
var suffix = FindSuffixFor(path);
var window = InstantiateWindow(path + suffix);

_windows.Add(window);
setupWindow?.Invoke(window);
window.OnHidden += WindowOnHidden;
InstantiateInputBlockIfNeeded(window);
window.Init();

window.Show();
OnWindowCreated(window);
return window;
}

private void InstantiateInputBlockIfNeeded(Window window)
{
if (!window.WithInputBlockForBackground)
return;

var inputBlock = Object.Instantiate(_inputBlockPrefab, window.transform);
inputBlock.name = inputBlock.name.Replace("(Clone)", string.Empty);
inputBlock.transform.SetAsFirstSibling();
}

private void DestroyInputBlockIfNotNeeded(Window window)
{
if (!window.WithInputBlockForBackground)
return;

var inputBlock = window.transform.GetChild(0);
Object.Destroy(inputBlock.gameObject);
}

void IWindowsManager.PrefetchWindow(string path)
{
var suffix = FindSuffixFor(path);
var window = InstantiateWindow(path + suffix);
window.gameObject.SetActive(false);
}

private Window InstantiateWindow(string fullWindowPath)
{
Window window;
var windowName = fullWindowPath.Split('/')[^1];
if (_invisibleWindows.ContainsKey(windowName))
{
var windowList = _invisibleWindows[windowName];
if (windowList.Count != 0)
{
window = windowList[^1];
window.gameObject.SetActive(true);
window.transform.SetAsLastSibling();
windowList.RemoveAt(windowList.Count - 1);
return window;
}
}

var windowPrefab = GetWindowPrefab(fullWindowPath);

window = Object.Instantiate(windowPrefab, _root).GetComponent<Window>() ??
throw new ArgumentException($"WindowsManager: missing Window script on window {fullWindowPath}");

window.name = window.name.Replace("(Clone)", string.Empty);

return window;
}

private void OnBackButtonPressedCallback()
{
for (var i = _windows.Count - 1; i >= 0; --i)
{
var curWindow = _windows[i];
if (!curWindow.ProcessBackButton)
continue;
curWindow.ProcessBackButtonPress();
break;
}
}

private string FindSuffixFor(string windowPath)
{
foreach (var (suffix, predicate) in _suffixesWithPredicates)
{
if (!predicate.Invoke())
continue;

var windowName = windowPath.Split('/')[^1];
if (_invisibleWindows.ContainsKey(windowName + suffix))
return suffix;

if (CheckWindowPrefabExistence(windowPath + suffix))
return suffix;
}

return string.Empty;
}

private bool CheckWindowPrefabExistence(string path)
{
if (_loadedWindowPrefabs.ContainsKey(path))
return true;
return Resources.Load<GameObject>(path) != null;
}

private GameObject GetWindowPrefab(string path)
{
GameObject windowPrefab;
if (_loadedWindowPrefabs.ContainsKey(path))
windowPrefab = _loadedWindowPrefabs[path];
else
{
windowPrefab = Resources.Load<GameObject>(path) ??
throw new ArgumentException($"Can't load prefab with such path {path}");

_loadedWindowPrefabs.Add(path, windowPrefab);
}

return windowPrefab;
}

private void WindowOnHidden(Window window)
{
window.OnHidden -= WindowOnHidden;

for (var i = 0; i < _windows.Count; ++i)
{
if (_windows[i] != window)
continue;
if (!window.DestroyOnClose)
{
DestroyInputBlockIfNotNeeded(window);
if (!_invisibleWindows.ContainsKey(window.name))
_invisibleWindows.Add(window.name, new List<Window> {window});
else
_invisibleWindows[window.name].Add(window);
}

_windows.RemoveAt(i);
OnWindowClosed.Invoke(window);
return;
}
}
}
}
Loading

0 comments on commit a544129

Please sign in to comment.