hellbound/Assets/Scripts/Game/UI/WindowsController.cs

447 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class WindowsController : MonoBehaviour
{
[SerializeField] private Canvas _canvas = null;
[SerializeField] private GameObject _windowContainer = null;
[Space]
[Header("Background:")]
[SerializeField] private RectTransform _background = null;
[SerializeField] private BackgroundController _backgroundController = null;
[SerializeField] private ClickHandlerMediator _backgroundClicker = null;
public IReadOnlyList<Window> GetVisibleStack() => _visibleWindows;
private List<Window> _visibleWindows = new List<Window>();
private readonly Dictionary<Type, Window> _windows = new Dictionary<Type, Window>();
private readonly List<Window> _hidingWindows = new List<Window>();
private readonly LinkedList<Action> _windowQueue = new LinkedList<Action>();
private readonly List<Window> _availableWindows = new List<Window>();
public Canvas Canvas => _canvas;
public void Init()
{
GetAvailableWindows();
SetCanvasSettings();
_backgroundClicker.OnClick += TryCloseActiveWindow;
_backgroundController.Init();
}
private void GetAvailableWindows()
{
Window[] windows = Assets.LoadAll<Window>("UI/Windows");
_availableWindows.AddRange(windows);
}
private void SetCanvasSettings()
{
_canvas.planeDistance = 1f;
_canvas.sortingOrder = 10;
}
private void TryCloseActiveWindow()
{
var window = GetTopmostWindow();
if (window == null)
return;
if (window.CanBeClosedByBackground && window.NeedShowBackground)
window.Close();
}
public T ShowWindow<T>() where T : ParameterlessWindow
{
var wnd = GetOrCreateWindow<T>();
wnd.Show();
return wnd;
}
public T ShowWindow<T, A1>(A1 a1) where T : ParameterWindow<A1>
{
var wnd = GetOrCreateWindow<T>();
wnd.Show(a1);
return wnd;
}
public T ShowWindow<T, A1, A2>(A1 a1, A2 a2) where T : ParameterWindow<A1, A2>
{
var wnd = GetOrCreateWindow<T>();
wnd.Show(a1, a2);
return wnd;
}
public bool IsWindowVisible<T>() where T : Window
{
return _visibleWindows.Find(x => x.GetType() == typeof(T)) != null;
}
public bool HasAnyVisible()
{
return _visibleWindows.Count > 0;
}
private void SortWindows()
{
if (IsUnsorted(_visibleWindows))
{
_visibleWindows = _visibleWindows.OrderByDescending(w => w.ZOrder).ToList();
}
for (var i = 0; i < _visibleWindows.Count; i++)
{
_visibleWindows[i].transform.SetSiblingIndex(i + 1);
}
}
private static bool IsUnsorted(IList<Window> windows_list)
{
var prev_z = int.MinValue;
for (var index = 0; index < windows_list.Count; index++)
{
var current_z = windows_list[index].ZOrder;
if (current_z > prev_z)
return true;
prev_z = current_z;
}
return false;
}
private void OnWindowShow(Window wnd)
{
if (!_visibleWindows.Contains(wnd))
_visibleWindows.Add(wnd);
SortWindows();
Log.Debug($"window show ID={wnd}, sibling_index={wnd.transform.GetSiblingIndex()}");
UpdateBackground(GetTopmostWindow());
}
private void OnWindowHide(Window wnd)
{
_visibleWindows.Remove(wnd);
_hidingWindows.Add(wnd);
wnd.OnShow += window => _hidingWindows.Remove(wnd); //для тех случаев, когда это же окно будет вновь открыто
wnd.OnAfterClose += window => _hidingWindows.Remove(wnd);
Log.Debug($"window hide ID={wnd}, window_stack= {ReportWindowStack()}");
UpdateBackground(GetTopmostWindow());
}
private void OnWindowClose(Window wnd)
{
_windows.Remove(wnd.GetType());
_visibleWindows.Remove(wnd);
}
private string ReportWindowStack()
{
var stack_report = "";
foreach (var wnd in _visibleWindows)
{
if (stack_report != "")
stack_report += ", ";
stack_report += wnd;
}
return stack_report == "" ? "EMPTY" : stack_report;
}
private Window GetTopmostWindow()
{
if (_visibleWindows.Count == 0)
return null;
return Enumerable.Last(_visibleWindows);
}
public string GetTopmostWindowName()
{
return GetTopmostWindow() != null ? GetTopmostWindow().name : null;
}
private void UpdateBackground(Window wnd)
{
if (wnd == null && _windowQueue.Count > 0)
return;
if (wnd == null || !wnd.NeedShowBackground)
{
HideBackground();
return;
}
PushBackgroundUnder(wnd);
StretchBackground();
ShowBackground();
}
private void HideBackground()
{
_backgroundController.Hide();
}
private void ShowBackground()
{
_backgroundController.Show();
}
private void StretchBackground()
{
const float stretch_overshoot = 100f; //for IPhone X, for other 5f is enough
_background.offsetMin = new Vector2(-stretch_overshoot, -stretch_overshoot);
_background.offsetMax = new Vector2(stretch_overshoot, stretch_overshoot);
}
private void PushBackgroundUnder(Window wnd)
{
_background.SetSiblingIndex(0);
_background.SetSiblingIndex(Math.Max(0, wnd.transform.GetSiblingIndex() - 1));
_background.anchoredPosition3D = Vector3.forward * wnd.ZOrder;
}
public T GetOrCreateWindow<T>() where T : Window
{
var window_type = typeof(T);
T window;
if (_windows.ContainsKey(window_type))
window = _windows[window_type] as T;
else
{
window = CreateWindow<T>();
window.OnShow += OnWindowShow;
window.OnHide += OnWindowHide;
window.OnClose += OnWindowClose;
window.OnAfterClose += OnAfterWindowClose;
window.gameObject.SetActive(false);
}
if (window == null)
{
Log.Error("Failed to create window TYPE={" + window_type + "}");
return null;
}
return window;
}
private void OnAfterWindowClose(Window wnd)
{
if (_windowQueue.Count == 0 || GetTopmostWindow() != null)
return;
var show_next_window = _windowQueue.First.Value;
_windowQueue.RemoveFirst();
show_next_window();
}
private T CreateWindow<T>() where T : Window
{
var resource = _availableWindows.Find(w => w as T);
var window = Assets.Create(resource as T);
var id = typeof(T);
_windows.Add(id, window);
window.gameObject.SetParent(_windowContainer.transform, false);
window.name = id.Name;
window.Init();
return window;
}
public void CloseVisibleStack()
{
Window[] to_close = new Window[_visibleWindows.Count];
_visibleWindows.CopyTo(to_close);
foreach (var wnd in to_close)
{
wnd.OnShow -= OnWindowShow;
wnd.OnHide -= OnWindowHide;
wnd.OnClose -= OnWindowClose;
wnd.OnAfterClose -= OnAfterWindowClose;
wnd.Close();
}
}
public void HideVisibleStack()
{
Window[] to_hide = new Window[_visibleWindows.Count];
_visibleWindows.CopyTo(to_hide);
foreach (var wnd in to_hide)
{
wnd.Hide();
}
}
public T GetWindow<T>() where T : Window
{
var window = GetOr(_windows, typeof(T));
return window as T;
}
public static V GetOr<K, V>(IDictionary<K, V> self, K key, V default_value = default(V))
{
V val;
return self.TryGetValue(key, out val) ? val : default_value;
}
public IEnumerator ShowWithDelay<T>(float delay = .2f) where T : ParameterlessWindow
{
var window = GetOrCreateWindow<T>();
yield return new WaitForSeconds(delay);
window.Show();
}
public void CloseWindow<T>() where T : Window
{
var window = GetWindow<T>();
if (window == null)
return;
window.Close();
}
public IEnumerator WaitForWindowClose<T>() where T : Window
{
var window = GetWindow<T>();
if (window == null)
yield break;
var is_closed = false;
window.OnAfterClose += w => is_closed = true;
yield return new WaitUntil(() => is_closed);
}
public IEnumerator CloseWindowAndWait<T>() where T : Window
{
var window = GetWindow<T>();
if (window == null)
yield break;
window.Close();
yield return WaitForWindowClose<T>();
}
public IEnumerator WaitForWindow<T>() where T : Window
{
while (GetWindow<T>() == null)
yield return null;
}
public IEnumerator WaitForWindowThenWaitForClose<T>() where T : Window
{
yield return WaitForWindow<T>();
yield return WaitForWindowClose<T>();
}
public IEnumerator WaitForWindowForTimeThenWaitForClose<T>(float time_seconds = 2) where T : Window
{
yield return WaitForWindowForTime<T>(time_seconds);
yield return WaitForWindowClose<T>();
}
public IEnumerator WaitForWindowForTime<T>(float time_seconds = 2) where T : Window
{
var time = 0f;
while (time < time_seconds && GetWindow<T>() == null)
{
time += Time.deltaTime;
yield return null;
}
}
public void ShowAfter<T>() where T : ParameterlessWindow
{
if (HasAnyVisible())
{
_windowQueue.AddLast(() => ShowWindow<T>());
return;
}
ShowWindow<T>();
}
public void ShowAfter<T>(Action<T> action) where T : ParameterlessWindow
{
_windowQueue.AddLast(() =>
{
Debug.LogError("ShowAfter invoke");
var window = GetOrCreateWindow<T>();
action(window);
window.Show();
});
}
public void ShowAfter<T, A1>(A1 a1) where T : ParameterWindow<A1>
{
if (HasAnyVisible())
{
_windowQueue.AddLast(() => ShowWindow<T, A1>(a1));
return;
}
ShowWindow<T, A1>(a1);
}
public void ShowAfter<T, A1, A2>(A1 a1, A2 a2) where T : ParameterWindow<A1, A2>
{
if (HasAnyVisible())
{
_windowQueue.AddLast(() => ShowWindow<T, A1, A2>(a1, a2));
return;
}
ShowWindow<T, A1, A2>(a1, a2);
}
public void ShowAfter<T, A1, A2>(A1 a1, A2 a2, Action<Window> on_close) where T : ParameterWindow<A1, A2>
{
Action show_window = () =>
{
var window = ShowWindow<T, A1, A2>(a1, a2);
window.OnAfterClose += on_close;
};
if (HasAnyVisible())
{
_windowQueue.AddLast(show_window);
return;
}
show_window();
}
public bool ThereIsNoHidingWindows()
{
return _hidingWindows.Count == 0;
}
public bool HasAnyOf(List<Type> check_types)
{
foreach (var window in _visibleWindows)
{
var index = check_types.IndexOf(window.GetType());
if (index > -1)
return true;
}
return false;
}
}