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

423 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}
}
}