OldBlueWater/BlueWater/Assets/Doozy/Runtime/UIManager/Input/BackButton.cs

423 lines
19 KiB
C#
Raw Normal View History

// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
// This code can only be used under the standard Unity Asset Store End User License Agreement
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
using System.Collections;
using System.Linq;
using Doozy.Runtime.Common;
using Doozy.Runtime.Common.Attributes;
using Doozy.Runtime.Common.Utils;
using Doozy.Runtime.Signals;
using Doozy.Runtime.UIManager.Components;
using Doozy.Runtime.UIManager.ScriptableObjects;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
#if INPUT_SYSTEM_PACKAGE
using UnityEngine.InputSystem.UI;
#endif
// ReSharper disable MemberCanBePrivate.Global
namespace Doozy.Runtime.UIManager.Input
{
/// <summary>
/// The Back Button functionality injects itself into the Input System and listens for the Cancel action.
/// It does that by automatically attaching a Input To Signal to the Event System.
/// This is an automated system that is activated by any UI component in DoozyUI.
/// </summary>
[AddComponentMenu("Doozy/Input/Back Button")]
[DisallowMultipleComponent]
public class BackButton : SingletonBehaviour<BackButton>
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("GameObject/Doozy/Input/Back Button", false, 8)]
private static void CreateComponent(UnityEditor.MenuCommand menuCommand)
{
GameObjectUtils.AddToScene<BackButton>("Back Button", false, true);
}
#endif
#if LEGACY_INPUT_MANAGER
public const KeyCode k_BackButtonKeyCode = KeyCode.Escape;
#endif
/// <summary> Default name of the virtual button that will be used for the 'Back' button, when LEGACY_INPUT_MANAGER is enabled </summary>
public const string k_BackButtonVirtualButtonName = "Cancel";
/// <summary> Stream category name for the Back Button </summary>
public const string k_StreamCategory = "Input";
/// <summary>
/// Stream name for the 'Back' Button.
/// Used by stream to listen for the Back Button.
/// </summary>
public const string k_StreamName = nameof(BackButton);
/// <summary>
/// Stream name for the 'Back' Button.
/// Used by streamIgnoreDisabled to listen for the 'Back' Button,
/// regardless if the 'Back' button functionality is enabled or not.
/// </summary>
public const string k_StreamNameIgnoreDisabled = k_StreamName + ".IgnoreDisabledState";
/// <summary>
/// Stream name for the 'Back' Button.
/// Used by streamOnEnabled to listen for when the 'Back' button functionality was enabled (from the disabled state)
/// </summary>
public const string k_StreamNameOnEnabled = k_StreamName + ".Enabled";
/// <summary>
/// Stream name for the 'Back' Button.
/// Used by streamOnDisabled to listen for when the 'Back' button functionality was disabled (from the enabled state)
/// </summary>
public const string k_StreamNameOnDisabled = k_StreamName + ".Disabled";
/// <summary> Default button name for the 'Back' Button </summary>
public const string k_ButtonName = "Back"; //ToDo: maybe allow for different button names to be THE 'Back' button
[ClearOnReload]
private static SignalStream s_stream;
[ClearOnReload]
private static SignalStream s_streamIgnoreDisabled;
[ClearOnReload]
private static SignalStream s_streamOnEnabled;
[ClearOnReload]
private static SignalStream s_streamOnDisabled;
/// <summary>
/// Stream that sends signals when the 'Back' button is fired.
/// This stream does not send signals when the 'Back' button is disabled.
/// </summary>
public static SignalStream stream => s_stream ??= SignalsService.GetStream(k_StreamCategory, k_StreamName);
/// <summary>
/// Stream that sends signals when the 'Back' button is fired.
/// This stream sends signals regardless if the 'Back' button is disabled or not.
/// </summary>
public static SignalStream streamIgnoreDisabled => s_streamIgnoreDisabled ??= SignalsService.GetStream(k_StreamCategory, k_StreamNameIgnoreDisabled);
/// <summary>
/// Stream that sends signals when the 'Back' button functionality was enabled (from the disabled state)
/// </summary>
public static SignalStream streamOnEnabled => s_streamOnEnabled ??= SignalsService.GetStream(k_StreamCategory, k_StreamNameOnEnabled);
/// <summary>
/// Stream that sends signals when the 'Back' button functionality was disabled (from the enabled state)
/// </summary>
public static SignalStream streamOnDisabled => s_streamOnDisabled ??= SignalsService.GetStream(k_StreamCategory, k_StreamNameOnDisabled);
[ClearOnReload]
private static SignalReceiver inputStreamReceiver { get; set; }
private static void ConnectToInputStream()
{
InputStream.Start();
InputStream.stream.ConnectReceiver(inputStreamReceiver);
}
private static void DisconnectFromInputStream()
{
InputStream.Stop();
InputStream.stream.DisconnectReceiver(inputStreamReceiver);
}
[ClearOnReload]
private static SignalReceiver buttonStreamReceiver { get; set; }
private static void ConnectToButtonStream() => UIButton.stream.ConnectReceiver(buttonStreamReceiver);
private static void DisconnectFromButtonStream() => UIButton.stream.DisconnectReceiver(buttonStreamReceiver);
/// <summary> Reference to the UIManager Input Settings </summary>
public static UIManagerInputSettings inputSettings => UIManagerInputSettings.instance;
/// <summary> True Multiplayer Mode is enabled </summary>
public static bool multiplayerMode => inputSettings.multiplayerMode;
private int m_BackButtonDisableLevel; //This is an additive bool so if == 0 --> false (the 'Back' button is NOT disabled) and if > 0 --> true (the 'Back' button is disabled).
private double m_LastTimeBackButtonWasExecuted; //Internal variable used to keep track when the 'Back' button was executed the last time
/// <summary> Cooldown after the 'Back' button was fired (to prevent spamming and accidental double execution) </summary>
public static float cooldown => inputSettings.backButtonCooldown;
/// <summary> True if the 'Back' button functionality is disabled </summary>
public bool isDisabled
{
get
{
if (m_BackButtonDisableLevel < 0) m_BackButtonDisableLevel = 0;
return m_BackButtonDisableLevel != 0;
}
}
/// <summary> True if the 'Back' button functionality is enabled </summary>
public bool isEnabled => !isDisabled;
/// <summary> True if the 'Back' button can be triggered again </summary>
public bool inCooldown => Time.realtimeSinceStartup - m_LastTimeBackButtonWasExecuted < cooldown;
/// <summary> True if the 'Back' button functionality is enabled and is not in cooldown </summary>
public bool canFire => isEnabled && !inCooldown;
/// <summary> Flag marked as True if a InputToSignal set to listen for the 'Back' button exists in the scene </summary>
public bool hasInput { get; private set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private bool initialized { get; set; }
public static void Initialize()
{
_ = instance;
}
protected override void Awake()
{
base.Awake();
initialized = false;
m_LastTimeBackButtonWasExecuted = Time.realtimeSinceStartup;
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
CheckForInput();
}
private IEnumerator Start()
{
yield return null;
CheckForInput();
initialized = hasInput;
inputStreamReceiver = new SignalReceiver().SetOnSignalCallback(signal =>
{
if (!signal.hasValue) return;
#if INPUT_SYSTEM_PACKAGE
if (!(signal.valueAsObject is InputSignalData { inputActionName: UIInputActionName.Cancel } data)) return;
Fire(data);
#endif
#if LEGACY_INPUT_MANAGER
if (!(signal.valueAsObject is InputSignalData data)) return;
switch (data.inputMode)
{
case LegacyInputMode.KeyCode:
if (data.keyCode == BackButton.k_BackButtonKeyCode)
Fire(data);
break;
case LegacyInputMode.VirtualButton:
if (data.virtualButtonName == inputSettings.backButtonVirtualButtonName)
Fire(data);
break;
case LegacyInputMode.None: break;
default: throw new System.ArgumentOutOfRangeException();
}
#endif
});
ConnectToInputStream();
buttonStreamReceiver = new SignalReceiver().SetOnSignalCallback(signal =>
{
if (!signal.hasValue)
return;
if (!(signal.valueAsObject is UIButtonSignalData data))
return;
if (!data.buttonName.Equals(k_ButtonName))
return;
#if INPUT_SYSTEM_PACKAGE
Fire(new InputSignalData(UIInputActionName.Cancel, data.playerIndex));
#endif
#if LEGACY_INPUT_MANAGER
Fire(new InputSignalData(LegacyInputMode.KeyCode, BackButton.k_BackButtonKeyCode, BackButton.k_BackButtonVirtualButtonName, data.playerIndex));
#endif
});
ConnectToButtonStream();
}
protected override void OnDestroy()
{
base.OnDestroy();
DisconnectFromInputStream();
DisconnectFromButtonStream();
SceneManager.sceneLoaded -= OnSceneLoaded;
}
//ToDo: maybe -> add option to set default action name for 'Back' button (instead of Cancel)
public void CheckForInput()
{
hasInput = false;
if (EventSystem.current == null && !multiplayerMode)
{
Debug.LogWarning
(
$"{nameof(EventSystem)}.current is null. " +
$"Add it to the scene to fix this issue."
);
return;
}
if (EventSystem.current != null)
{
AddInputToSignalToGameObject(EventSystem.current.gameObject);
hasInput = true;
}
if (!multiplayerMode) //multiplayer mode disabled -> stop here
return;
#if INPUT_SYSTEM_PACKAGE
MultiplayerEventSystem[] multiplayerEventSystems = FindObjectsOfType<MultiplayerEventSystem>();
if (!hasInput || multiplayerEventSystems == null || multiplayerEventSystems.Length == 0)
{
Debug.LogWarning
(
$"MultiplayerMode -> No {nameof(MultiplayerEventSystem)} found. " +
$"Add at least one to the scene to fix this issue."
);
return;
}
foreach (MultiplayerEventSystem eventSystem in multiplayerEventSystems)
AddInputToSignalToGameObject(eventSystem.gameObject);
#endif
hasInput = true;
}
/// <summary>
/// Execute the 'Back' button event, only if can fire and is enabled.
/// This method is used to simulate a 'Back' button
/// </summary>
public static void Fire(InputSignalData data)
{
if (instance == null) return; //instance is null when the application is quitting
if (instance.inCooldown) return; //if the 'Back' button is in cooldown, don't execute anything (to prevent spamming)
instance.m_LastTimeBackButtonWasExecuted = Time.realtimeSinceStartup; //update the last time the 'Back' button was executed
streamIgnoreDisabled.SendSignal(data); //send a MetaSignal (with input data) on the secondary stream, regardless of the 'Back' button's disabled state or if it is in cooldown
if (!instance.isEnabled) return; //if the 'Back' button is disabled, don't send the signal on the primary stream
stream.SendSignal(data); //send a MetaSignal (with input data) on the main stream
}
/// <summary>
/// Execute the 'Back' button event, only if can fire and is enabled.
/// This method is used to simulate a 'Back' button
/// </summary>
public static void Fire()
{
if (instance == null) return; //instance is null when the application is quitting
if (instance.inCooldown) return; //if the 'Back' button is in cooldown, don't execute anything (to prevent spamming)
streamIgnoreDisabled.SendSignal(); //send a Signal (without input data) (ping) on the secondary stream, regardless of the 'Back' button's disabled state or if it is in cooldown
instance.m_LastTimeBackButtonWasExecuted = Time.realtimeSinceStartup; //update the last time the 'Back' button was executed
if (!instance.isEnabled) return; //if the 'Back' button is disabled, don't send the signal on the primary stream
stream.SendSignal(); //send a Signal (without input data) (ping) on the main stream
}
/// <summary> True if the 'Back' button functionality is enabled </summary>
public static bool IsEnabled() => instance != null && instance.isEnabled;
/// <summary> True if the 'Back' button functionality is disabled </summary>
public static bool IsDisabled() => instance != null && instance.isDisabled;
/// <summary> Disable the 'Back' button functionality </summary>
public static void Disable()
{
if (instance == null) return; //instance is null when the application is quitting
if (instance.isEnabled) streamOnDisabled.SendSignal($"{nameof(BackButton)}.{nameof(Disable)}"); //send a signal on the streamOnDisabled stream
instance.m_BackButtonDisableLevel++; //if == 0 --> false (back button is not disabled) if > 0 --> true (back button is disabled)
}
/// <summary> Enable the 'Back' button functionality </summary>
public static void Enable()
{
if (instance == null) return; //instance is null when the application is quitting
instance.m_BackButtonDisableLevel--; //if == 0 --> false (back button is not disabled) if > 0 --> true (back button is disabled)
if (instance.m_BackButtonDisableLevel < 0) instance.m_BackButtonDisableLevel = 0; //clamp the value to zero
if (instance.isEnabled) streamOnEnabled.SendSignal($"{nameof(BackButton)}.{nameof(Enable)}"); //send a signal on the streamOnEnabled stream
}
/// <summary>
/// Enable the 'Back' button functionality by resetting the additive bool to zero. backButtonDisableLevel = 0.
/// Use this ONLY for special cases when something wrong happens and the back button is stuck in disabled mode.
/// </summary>
public static void EnableByForce()
{
if (instance == null) return; //instance is null when the application is quitting
instance.m_BackButtonDisableLevel = 0; //reset the additive bool to zero
streamOnEnabled.SendSignal($"{nameof(BackButton)}.{nameof(EnableByForce)}"); //send a signal on the streamOnEnabled stream
}
/// <summary>
/// Checks if the given target has an InputToSignal that triggers the 'Back' button.
/// If no such InputToSignal is found, one is added automatically
/// </summary>
/// <param name="target"> Target gameObject </param>
private static void AddInputToSignalToGameObject(GameObject target)
{
#if !INPUT_SYSTEM_PACKAGE && !LEGACY_INPUT_MANAGER
return;
#endif
#pragma warning disable CS0162
//search that there is at least one InputToSignal able to trigger the 'Back' button; if not, add it
InputToSignal[] inputsToSignal = target.GetComponents<InputToSignal>();
if
(
inputsToSignal == null ||
inputsToSignal.Length == 0 ||
!inputsToSignal.Any(i => i.SendsBackButtonSignal())
)
{
#if INPUT_SYSTEM_PACKAGE
target
.AddComponent<InputToSignal>()
.ConnectToAction(UIInputActionName.Cancel);
#endif
#if LEGACY_INPUT_MANAGER
InputToSignal its = target.AddComponent<InputToSignal>();
its.inputMode = LegacyInputMode.KeyCode;
its.keyCode = BackButton.k_BackButtonKeyCode;
its.virtualButtonName = BackButton.k_BackButtonVirtualButtonName;
#endif
}
#pragma warning restore CS0162
}
}
public static class BackButtonExtras
{
public static bool SendsBackButtonSignal<T>(this T target) where T : InputToSignal
{
#if INPUT_SYSTEM_PACKAGE
return target != null &&
target.isConnected &&
target.inputActionName.Equals(UIInputActionName.Cancel.ToString());
#endif
#if LEGACY_INPUT_MANAGER
return
target != null &&
target.inputMode switch
{
LegacyInputMode.None => false,
LegacyInputMode.KeyCode => target.keyCode == BackButton.k_BackButtonKeyCode,
LegacyInputMode.VirtualButton => target.virtualButtonName == BackButton.k_BackButtonVirtualButtonName,
_ => false
};
#endif
#pragma warning disable CS0162
// ReSharper disable once HeuristicUnreachableCode
return false;
#pragma warning restore CS0162
}
}
}