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 GetVisibleStack() => _visibleWindows; private List _visibleWindows = new List(); private readonly Dictionary _windows = new Dictionary(); private readonly List _hidingWindows = new List(); private readonly LinkedList _windowQueue = new LinkedList(); private readonly List _availableWindows = new List(); public Canvas Canvas => _canvas; public void Init() { GetAvailableWindows(); SetCanvasSettings(); _backgroundClicker.OnClick += TryCloseActiveWindow; _backgroundController.Init(); } private void GetAvailableWindows() { Window[] windows = Assets.LoadAll("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() where T : ParameterlessWindow { var wnd = GetOrCreateWindow(); wnd.Show(); return wnd; } public T ShowWindow(A1 a1) where T : ParameterWindow { var wnd = GetOrCreateWindow(); wnd.Show(a1); return wnd; } public T ShowWindow(A1 a1, A2 a2) where T : ParameterWindow { var wnd = GetOrCreateWindow(); wnd.Show(a1, a2); return wnd; } public bool IsWindowVisible() 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 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() where T : Window { var window_type = typeof(T); T window; if (_windows.ContainsKey(window_type)) window = _windows[window_type] as T; else { window = CreateWindow(); 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() 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() where T : Window { var window = GetOr(_windows, typeof(T)); return window as T; } public static V GetOr(IDictionary self, K key, V default_value = default(V)) { V val; return self.TryGetValue(key, out val) ? val : default_value; } public IEnumerator ShowWithDelay(float delay = .2f) where T : ParameterlessWindow { var window = GetOrCreateWindow(); yield return new WaitForSeconds(delay); window.Show(); } public void CloseWindow() where T : Window { var window = GetWindow(); if (window == null) return; window.Close(); } public IEnumerator WaitForWindowClose() where T : Window { var window = GetWindow(); if (window == null) yield break; var is_closed = false; window.OnAfterClose += w => is_closed = true; yield return new WaitUntil(() => is_closed); } public IEnumerator CloseWindowAndWait() where T : Window { var window = GetWindow(); if (window == null) yield break; window.Close(); yield return WaitForWindowClose(); } public IEnumerator WaitForWindow() where T : Window { while (GetWindow() == null) yield return null; } public IEnumerator WaitForWindowThenWaitForClose() where T : Window { yield return WaitForWindow(); yield return WaitForWindowClose(); } public IEnumerator WaitForWindowForTimeThenWaitForClose(float time_seconds = 2) where T : Window { yield return WaitForWindowForTime(time_seconds); yield return WaitForWindowClose(); } public IEnumerator WaitForWindowForTime(float time_seconds = 2) where T : Window { var time = 0f; while (time < time_seconds && GetWindow() == null) { time += Time.deltaTime; yield return null; } } public void ShowAfter() where T : ParameterlessWindow { if (HasAnyVisible()) { _windowQueue.AddLast(() => ShowWindow()); return; } ShowWindow(); } public void ShowAfter(Action action) where T : ParameterlessWindow { _windowQueue.AddLast(() => { Debug.LogError("ShowAfter invoke"); var window = GetOrCreateWindow(); action(window); window.Show(); }); } public void ShowAfter(A1 a1) where T : ParameterWindow { if (HasAnyVisible()) { _windowQueue.AddLast(() => ShowWindow(a1)); return; } ShowWindow(a1); } public void ShowAfter(A1 a1, A2 a2) where T : ParameterWindow { if (HasAnyVisible()) { _windowQueue.AddLast(() => ShowWindow(a1, a2)); return; } ShowWindow(a1, a2); } public void ShowAfter(A1 a1, A2 a2, Action on_close) where T : ParameterWindow { Action show_window = () => { var window = ShowWindow(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 check_types) { foreach (var window in _visibleWindows) { var index = check_types.IndexOf(window.GetType()); if (index > -1) return true; } return false; } }