255 lines
7.0 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace ConjureOS.ArcadeMenu
{
[RequireComponent(typeof(Animator))]
public class ConjureArcadeMenu : MonoBehaviour
{
public static event Action OnOpen;
public static event Action OnClose;
public static event Action<bool> OnStatusChanged;
private static ConjureArcadeMenu Instance { get; set; }
public static bool HasInstance => (bool) Instance;
private bool isOpened;
public static bool IsOpened => HasInstance && Instance.IsOpenedOnInstance;
private bool IsOpenedOnInstance
{
get => isOpened;
set
{
if (value != isOpened)
{
if (value)
{
OnOpen?.Invoke();
}
else
{
OnClose?.Invoke();
}
OnStatusChanged?.Invoke(value);
}
isOpened = value;
}
}
private ConjureArcadeMenuButton SelectedButton
{
get
{
if (currentSelectedButtonIndex < 0 || currentSelectedButtonIndex >= menuButtons.Count)
{
return null;
}
return menuButtons[currentSelectedButtonIndex];
}
}
[SerializeField]
private List<ConjureArcadeMenuButton> menuButtons;
[SerializeField]
private ConjureArcadeMenuButton firstMenuButton;
private bool IsAnimatingOpen => openAnimationCoroutine != null;
private bool IsAnimatingClose => closeAnimationCoroutine != null;
private Coroutine openAnimationCoroutine;
private Coroutine closeAnimationCoroutine;
private Animator animator;
private int currentSelectedButtonIndex = -1;
public static void Open()
{
if (HasInstance)
{
Instance.OpenOnInstance();
}
}
public static void Close()
{
if (HasInstance)
{
Instance.CloseOnInstance();
}
}
public static void MovePrevious()
{
if (HasInstance)
{
Instance.MovePreviousOnInstance();
}
}
public static void MoveNext()
{
if (HasInstance)
{
Instance.MoveNextOnInstance();
}
}
public static void Select()
{
if (HasInstance)
{
Instance.SelectOnInstance();
}
}
private void OpenOnInstance()
{
if (IsOpenedOnInstance)
{
return;
}
int indexOnOpen = menuButtons.IndexOf(firstMenuButton);
if (indexOnOpen == -1)
{
indexOnOpen = 0;
}
UpdateSelectedButtonIndex(indexOnOpen);
IsOpenedOnInstance = true;
openAnimationCoroutine = StartCoroutine(AnimateOpenRoutine());
}
private void CloseOnInstance()
{
if (!IsOpenedOnInstance)
{
return;
}
IsOpenedOnInstance = false;
closeAnimationCoroutine = StartCoroutine(AnimateCloseRoutine());
}
private void MovePreviousOnInstance()
{
if (!IsOpenedOnInstance || menuButtons.Count == 0)
{
return;
}
UpdateSelectedButtonIndex(currentSelectedButtonIndex - 1 < 0 ? menuButtons.Count - 1 : currentSelectedButtonIndex - 1);
}
private void MoveNextOnInstance()
{
if (!IsOpenedOnInstance || menuButtons.Count == 0)
{
return;
}
UpdateSelectedButtonIndex((currentSelectedButtonIndex + 1) % menuButtons.Count);
}
private void UpdateSelectedButtonIndex(int newButtonIndex)
{
if (SelectedButton != null)
{
SelectedButton.IsSelected = false;
}
currentSelectedButtonIndex = newButtonIndex;
if (SelectedButton != null)
{
SelectedButton.IsSelected = true;
}
}
private void SelectOnInstance()
{
if (!IsOpenedOnInstance)
{
return;
}
if (currentSelectedButtonIndex < 0 || currentSelectedButtonIndex >= menuButtons.Count)
{
return;
}
ConjureArcadeMenuButton buttonToSelect = menuButtons[currentSelectedButtonIndex];
if (!buttonToSelect)
{
return;
}
buttonToSelect.Execute();
}
private IEnumerator AnimateOpenRoutine()
{
if (animator && !IsAnimatingOpen)
{
float normalizedTimeToStart = 0.0f;
if (IsAnimatingClose)
{
normalizedTimeToStart = 1.0f - animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
StopCoroutine(AnimateCloseRoutine());
closeAnimationCoroutine = null;
}
animator.Play("Open", 0, normalizedTimeToStart);
yield return new WaitUntil(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.99f);
}
openAnimationCoroutine = null;
}
private IEnumerator AnimateCloseRoutine()
{
if (animator && !IsAnimatingClose)
{
float normalizedTimeToStart = 0.0f;
if (IsAnimatingOpen)
{
normalizedTimeToStart = 1.0f - animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
StopCoroutine(AnimateOpenRoutine());
openAnimationCoroutine = null;
}
animator.Play("Close", 0, normalizedTimeToStart);
yield return new WaitUntil(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.99f);
}
closeAnimationCoroutine = null;
}
private void Awake()
{
animator = GetComponent<Animator>();
}
private void Start()
{
if (HasInstance)
{
Destroy(gameObject);
return;
}
Instance = this;
// To adjust the layout groups at the start when the window hasn't had time to resize yet.
// If this is not called, the layout might not work correctly.
LayoutRebuilder.ForceRebuildLayoutImmediate(GetComponent<RectTransform>());
}
}
}