457 lines
19 KiB
C#
457 lines
19 KiB
C#
// 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;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Doozy.Runtime.Common.Utils;
|
|
using Doozy.Runtime.UIManager.Events;
|
|
using Doozy.Runtime.UIManager.Input;
|
|
using Doozy.Runtime.UIManager.ScriptableObjects;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
// ReSharper disable MemberCanBeProtected.Global
|
|
// ReSharper disable MemberCanBePrivate.Global
|
|
|
|
namespace Doozy.Runtime.UIManager.Components
|
|
{
|
|
/// <summary> UI object used to create a selectable control. This class is derived from Unity's Selectable class </summary>
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("Doozy/UI/Components/UISelectable")]
|
|
[SelectionBase]
|
|
public partial class UISelectable : Selectable, ICanvasElement, IUseMultiplayerInfo
|
|
{
|
|
#if UNITY_EDITOR
|
|
[UnityEditor.MenuItem("GameObject/Doozy/UI/Components/UISelectable", false, 8)]
|
|
private static void CreateComponent(UnityEditor.MenuCommand menuCommand)
|
|
{
|
|
GameObjectUtils.AddToScene<UISelectable>("UISelectable", false, true);
|
|
}
|
|
#endif
|
|
|
|
public const string k_StreamCategory = nameof(UISelectable);
|
|
public const float k_DefaultAnimationDuration = 0.2f;
|
|
|
|
#region MultiplayerInfo
|
|
|
|
[SerializeField] private MultiplayerInfo MultiplayerInfo;
|
|
public MultiplayerInfo multiplayerInfo => MultiplayerInfo;
|
|
public bool hasMultiplayerInfo => multiplayerInfo != null;
|
|
public int playerIndex => multiplayerMode & hasMultiplayerInfo ? multiplayerInfo.playerIndex : inputSettings.defaultPlayerIndex;
|
|
public void SetMultiplayerInfo(MultiplayerInfo reference) => MultiplayerInfo = reference;
|
|
|
|
#endregion
|
|
|
|
/// <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;
|
|
|
|
public enum SelectableType
|
|
{
|
|
Button,
|
|
Toggle
|
|
}
|
|
|
|
/// <summary> Selectable type </summary>
|
|
public virtual SelectableType selectableType => SelectableType.Button;
|
|
|
|
/// <summary> Returns TRUE if the selectable type is Button </summary>
|
|
public bool isButton => selectableType == SelectableType.Button;
|
|
|
|
/// <summary> Returns TRUE if the selectable type is Toggle </summary>
|
|
public bool isToggle => selectableType == SelectableType.Toggle;
|
|
|
|
[SerializeField] internal bool IsOn;
|
|
/// <summary> TRUE or FALSE for Toggle selectable type </summary>
|
|
public virtual bool isOn
|
|
{
|
|
get => true;
|
|
// ReSharper disable once ValueParameterNotUsed
|
|
set => IsOn = true;
|
|
}
|
|
|
|
private static IEnumerable<UISelectionState> s_uiSelectionStates;
|
|
/// <summary> Enumeration of all the UISelectionState enum values </summary>
|
|
public static IEnumerable<UISelectionState> uiSelectionStates => s_uiSelectionStates ?? (s_uiSelectionStates = Enum.GetValues(typeof(UISelectionState)).Cast<UISelectionState>());
|
|
|
|
/// <summary> Copy of the array of all the UISelectable objects currently active in the scene. </summary>
|
|
public static UISelectable[] allUISelectablesArray =>
|
|
allSelectablesArray.Where(selectable => selectable is UISelectable).Cast<UISelectable>().ToArray();
|
|
|
|
private RectTransform m_RectTransform;
|
|
/// <summary> Reference to the RectTransform component </summary>
|
|
public RectTransform rectTransform => m_RectTransform ? m_RectTransform : m_RectTransform = GetComponent<RectTransform>();
|
|
|
|
[SerializeField] private UISelectionState CurrentUISelectionState;
|
|
public UISelectionState currentUISelectionState => CurrentUISelectionState;
|
|
|
|
[SerializeField] private bool DeselectAfterPress;
|
|
public bool deselectAfterPress
|
|
{
|
|
get => DeselectAfterPress;
|
|
set => DeselectAfterPress = value;
|
|
}
|
|
|
|
/// <summary> UISelectionState changed - callback invoked when selection state changed </summary>
|
|
public UISelectionStateEvent OnSelectionStateChangedCallback = new UISelectionStateEvent();
|
|
|
|
[SerializeField] private string CurrentStateName;
|
|
/// <summary> Name of the current selection state </summary>
|
|
public string currentStateName => CurrentStateName;
|
|
|
|
[SerializeField] private UISelectableState NormalState = new UISelectableState(UISelectionState.Normal);
|
|
/// <summary> Callbacks for the Normal selection state </summary>
|
|
public UISelectableState normalState => NormalState;
|
|
|
|
[SerializeField] private UISelectableState HighlightedState = new UISelectableState(UISelectionState.Highlighted);
|
|
/// <summary> Callbacks for the Highlighted selection state </summary>
|
|
public UISelectableState highlightedState => HighlightedState;
|
|
|
|
[SerializeField] private UISelectableState PressedState = new UISelectableState(UISelectionState.Pressed);
|
|
/// <summary> Callbacks for the Pressed selection state </summary>
|
|
public UISelectableState pressedState => PressedState;
|
|
|
|
[SerializeField] private UISelectableState SelectedState = new UISelectableState(UISelectionState.Selected);
|
|
/// <summary> Callbacks for the Selected selection state </summary>
|
|
public UISelectableState selectedState => SelectedState;
|
|
|
|
[SerializeField] private UISelectableState DisabledState = new UISelectableState(UISelectionState.Disabled);
|
|
/// <summary> Callbacks for the disabled selection state </summary>
|
|
public UISelectableState disabledState => DisabledState;
|
|
|
|
[SerializeField] private UIBehaviours Behaviours;
|
|
/// <summary> Manages UIBehaviour components </summary>
|
|
public UIBehaviours behaviours => Behaviours;
|
|
|
|
#region UIBehaviour shortcuts
|
|
|
|
/// <summary> PointerEnter UIBehaviour </summary>
|
|
public UIBehaviour onPointerEnterBehaviour => AddBehaviour(UIBehaviour.Name.PointerEnter);
|
|
|
|
/// <summary> PointerExit UIBehaviour </summary>
|
|
public UIBehaviour onPointerExitBehaviour => AddBehaviour(UIBehaviour.Name.PointerExit);
|
|
|
|
/// <summary> PointerDown UIBehaviour </summary>
|
|
public UIBehaviour onPointerDownBehaviour => AddBehaviour(UIBehaviour.Name.PointerDown);
|
|
|
|
/// <summary> PointerUp UIBehaviour </summary>
|
|
public UIBehaviour onPointerUpBehaviour => AddBehaviour(UIBehaviour.Name.PointerUp);
|
|
|
|
/// <summary> PointerClick UIBehaviour </summary>
|
|
public UIBehaviour onClickBehaviour => AddBehaviour(UIBehaviour.Name.PointerClick);
|
|
|
|
/// <summary> PointerDoubleClick UIBehaviour </summary>
|
|
public UIBehaviour onDoubleClickBehaviour => AddBehaviour(UIBehaviour.Name.PointerDoubleClick);
|
|
|
|
/// <summary> PointerLongClick UIBehaviour </summary>
|
|
public UIBehaviour onLongClickBehaviour => AddBehaviour(UIBehaviour.Name.PointerLongClick);
|
|
|
|
/// <summary> PointerLeftClick UIBehaviour </summary>
|
|
public UIBehaviour onLeftClickBehaviour => AddBehaviour(UIBehaviour.Name.PointerLeftClick);
|
|
|
|
/// <summary> PointerMiddleClick UIBehaviour </summary>
|
|
public UIBehaviour onMiddleClickBehaviour => AddBehaviour(UIBehaviour.Name.PointerMiddleClick);
|
|
|
|
/// <summary> PointerRightClick UIBehaviour </summary>
|
|
public UIBehaviour onRightClickBehaviour => AddBehaviour(UIBehaviour.Name.PointerRightClick);
|
|
|
|
/// <summary> Selected UIBehaviour </summary>
|
|
public UIBehaviour onSelectedBehaviour => AddBehaviour(UIBehaviour.Name.Selected);
|
|
|
|
/// <summary> Deselected UIBehaviour </summary>
|
|
public UIBehaviour onDeselectedBehaviour => AddBehaviour(UIBehaviour.Name.Deselected);
|
|
|
|
/// <summary> Submit UIBehaviour </summary>
|
|
public UIBehaviour onSubmitBehaviour => AddBehaviour(UIBehaviour.Name.Submit);
|
|
|
|
#endregion
|
|
|
|
#region UnityEvent shortcuts from UIBehaviour
|
|
|
|
/// <summary> PointerEnter UnityEvent </summary>
|
|
public UnityEvent onPointerEnterEvent => onPointerEnterBehaviour.Event;
|
|
|
|
/// <summary> PointerExit UnityEvent </summary>
|
|
public UnityEvent onPointerExitEvent => onPointerExitBehaviour.Event;
|
|
|
|
/// <summary> PointerDown UnityEvent </summary>
|
|
public UnityEvent onPointerDownEvent => onPointerDownBehaviour.Event;
|
|
|
|
/// <summary> PointerUp UnityEvent </summary>
|
|
public UnityEvent onPointerUpEvent => onPointerUpBehaviour.Event;
|
|
|
|
/// <summary> PointerClick UnityEvent </summary>
|
|
public UnityEvent onClickEvent => onClickBehaviour.Event;
|
|
|
|
/// <summary> PointerDoubleClick UnityEvent </summary>
|
|
public UnityEvent onDoubleClickEvent => onDoubleClickBehaviour.Event;
|
|
|
|
/// <summary> PointerLongClick UnityEvent </summary>
|
|
public UnityEvent onLongClickEvent => onLongClickBehaviour.Event;
|
|
|
|
/// <summary> PointerLeftClick UnityEvent </summary>
|
|
public UnityEvent onLeftClickEvent => onLeftClickBehaviour.Event;
|
|
|
|
/// <summary> PointerMiddleClick UnityEvent </summary>
|
|
public UnityEvent onMiddleClickEvent => onMiddleClickBehaviour.Event;
|
|
|
|
/// <summary> PointerRightClick UnityEvent </summary>
|
|
public UnityEvent onRightClickEvent => onRightClickBehaviour.Event;
|
|
|
|
/// <summary> Selected UnityEvent </summary>
|
|
public UnityEvent onSelectedEvent => onSelectedBehaviour.Event;
|
|
|
|
/// <summary> Deselected UnityEvent </summary>
|
|
public UnityEvent onDeselectedEvent => onDeselectedBehaviour.Event;
|
|
|
|
/// <summary> Submit UnityEvent </summary>
|
|
public UnityEvent onSubmitEvent => onSubmitBehaviour.Event;
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Cooldown time in seconds before the selectable can be interacted with again.
|
|
/// <para/> This is useful when you want to prevent the selectable from being clicked multiple times in a short period of time.
|
|
/// </summary>
|
|
public float Cooldown;
|
|
|
|
/// <summary>
|
|
/// Set the interactable state to false during the cooldown time (and set selectable state to disabled).
|
|
/// </summary>
|
|
public bool DisableWhenInCooldown;
|
|
|
|
/// <summary> Internal coroutine to handle the cooldown. </summary>
|
|
private Coroutine cooldownRoutine { get; set; }
|
|
|
|
/// <summary> Flag to indicate if the selectable is in cooldown. </summary>
|
|
public bool inCooldown { get; protected set; }
|
|
|
|
private bool selectableInitialized { get; set; }
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
protected override void OnValidate()
|
|
{
|
|
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
|
|
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
|
|
base.OnValidate();
|
|
}
|
|
|
|
protected override void Reset()
|
|
{
|
|
base.Reset();
|
|
targetGraphic = null;
|
|
transition = Transition.None;
|
|
inCooldown = false;
|
|
}
|
|
|
|
#endif // if UNITY_EDITOR
|
|
|
|
#region ICanvasElement
|
|
|
|
public virtual void Rebuild(CanvasUpdate executing) {}
|
|
public virtual void LayoutComplete() {}
|
|
public virtual void GraphicUpdateComplete() {}
|
|
|
|
#endregion
|
|
|
|
public UISelectable()
|
|
{
|
|
Behaviours =
|
|
new UIBehaviours()
|
|
.SetSelectable(this);
|
|
}
|
|
|
|
protected override void Awake()
|
|
{
|
|
if (Application.isPlaying)
|
|
BackButton.Initialize();
|
|
|
|
targetGraphic = null;
|
|
transition = Transition.None;
|
|
m_RectTransform = GetComponent<RectTransform>();
|
|
selectableInitialized = false;
|
|
Behaviours
|
|
.SetSelectable(this)
|
|
.SetSignalSource(gameObject);
|
|
|
|
inCooldown = false;
|
|
}
|
|
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
RefreshState();
|
|
}
|
|
|
|
protected override void OnEnable()
|
|
{
|
|
if (Application.isPlaying)
|
|
{
|
|
BackButton.Initialize();
|
|
}
|
|
base.OnEnable();
|
|
if (!Application.isPlaying) return;
|
|
if (selectableInitialized) RefreshState();
|
|
StartCoroutine(ConnectBehaviours());
|
|
inCooldown = false;
|
|
}
|
|
|
|
protected override void OnDisable()
|
|
{
|
|
base.OnDisable();
|
|
behaviours.Disconnect();
|
|
inCooldown = false;
|
|
}
|
|
|
|
private IEnumerator ConnectBehaviours()
|
|
{
|
|
yield return null;
|
|
if(behaviours?.behaviours == null) yield break;
|
|
if(behaviours.behaviours.Count == 0) yield break;
|
|
behaviours
|
|
.SetSelectable(this)
|
|
.SetSignalSource(gameObject)
|
|
.Connect();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the given behaviour and get a reference to it (automatically connects)
|
|
/// If the behaviour already exists, the reference to it will get automatically returned.
|
|
/// </summary>
|
|
/// <param name="behaviourName"> UIBehaviour.Name </param>
|
|
public UIBehaviour AddBehaviour(UIBehaviour.Name behaviourName) =>
|
|
behaviours.AddBehaviour(behaviourName);
|
|
|
|
/// <summary> Remove the given behaviour (automatically disconnects) </summary>
|
|
/// <param name="behaviourName"> UIBehaviour.Name </param>
|
|
public void RemoveBehaviour(UIBehaviour.Name behaviourName) =>
|
|
behaviours.RemoveBehaviour(behaviourName);
|
|
|
|
/// <summary> Check if the given behaviour has been added (exists) </summary>
|
|
/// <param name="behaviourName"> UIBehaviour.Name </param>
|
|
public bool HasBehaviour(UIBehaviour.Name behaviourName) =>
|
|
behaviours.HasBehaviour(behaviourName);
|
|
|
|
/// <summary>
|
|
/// Get the behaviour with the given name.
|
|
/// Returns null if the behaviour has not been added (does not exist)
|
|
/// </summary>
|
|
/// <param name="behaviourName"> UIBehaviour.Name </param>
|
|
public UIBehaviour GetBehaviour(UIBehaviour.Name behaviourName) =>
|
|
behaviours.GetBehaviour(behaviourName);
|
|
|
|
protected override void InstantClearState()
|
|
{
|
|
base.InstantClearState();
|
|
|
|
if (currentUISelectionState != UISelectionState.Normal)
|
|
SetState(UISelectionState.Normal);
|
|
}
|
|
|
|
protected override void DoStateTransition(SelectionState state, bool instant)
|
|
{
|
|
if (!gameObject.activeInHierarchy)
|
|
return;
|
|
|
|
if (selectableInitialized & currentUISelectionState == GetUISelectionState(state))
|
|
return;
|
|
|
|
SetState(GetUISelectionState(state));
|
|
}
|
|
|
|
/// <summary> Set (by force) the UISelectable to the given selection state </summary>
|
|
/// <param name="state"> Target selection state </param>
|
|
public UISelectable SetState(UISelectionState state)
|
|
{
|
|
selectableInitialized = true;
|
|
if (deselectAfterPress && CurrentUISelectionState == UISelectionState.Pressed && state == UISelectionState.Selected)
|
|
{
|
|
EventSystem.current.SetSelectedGameObject(null);
|
|
// state = UISelectionState.Normal;
|
|
}
|
|
OnSelectionStateChangedCallback?.Invoke(state);
|
|
CurrentUISelectionState = state;
|
|
CurrentStateName = state.ToString();
|
|
GetUISelectableState(state).stateEvent.Execute();
|
|
return this;
|
|
}
|
|
|
|
public UISelectable RefreshState() =>
|
|
SetState(currentUISelectionState);
|
|
|
|
/// <summary> Get a reference to the UISelectableState for the given selection state </summary>
|
|
/// <param name="state"> Target selection state </param>
|
|
public UISelectableState GetUISelectableState(UISelectionState state) =>
|
|
state switch
|
|
{
|
|
UISelectionState.Normal => normalState,
|
|
UISelectionState.Highlighted => highlightedState,
|
|
UISelectionState.Pressed => pressedState,
|
|
UISelectionState.Selected => selectedState,
|
|
UISelectionState.Disabled => disabledState,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(state), state, null)
|
|
};
|
|
|
|
/// <summary> Get a reference to the current UISelectableState </summary>
|
|
public UISelectableState GetCurrentUISelectableState() =>
|
|
GetUISelectableState(currentUISelectionState);
|
|
|
|
/// <summary>
|
|
/// Convert a SelectionState to a UISelectionState
|
|
/// <para/> This is needed because the SelectedState enum (in the Selectable class) is set to protected instead of public (THANKS UNITY)
|
|
/// </summary>
|
|
/// <param name="selectionState"> Selection state to convert </param>
|
|
private static UISelectionState GetUISelectionState(SelectionState selectionState) =>
|
|
selectionState switch
|
|
{
|
|
SelectionState.Normal => UISelectionState.Normal,
|
|
SelectionState.Highlighted => UISelectionState.Highlighted,
|
|
SelectionState.Pressed => UISelectionState.Pressed,
|
|
SelectionState.Selected => UISelectionState.Selected,
|
|
SelectionState.Disabled => UISelectionState.Disabled,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(selectionState), selectionState, null)
|
|
};
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary> Start the cooldown </summary>
|
|
protected void StartCooldown()
|
|
{
|
|
StopCooldown();
|
|
if (Cooldown <= 0) return;
|
|
cooldownRoutine = StartCoroutine(CooldownRoutine());
|
|
}
|
|
|
|
/// <summary> Stop the cooldown </summary>
|
|
protected void StopCooldown()
|
|
{
|
|
inCooldown = false;
|
|
if (DisableWhenInCooldown) interactable = true;
|
|
if (cooldownRoutine == null) return;
|
|
StopCoroutine(cooldownRoutine);
|
|
cooldownRoutine = null;
|
|
}
|
|
|
|
/// <summary> Internal coroutine to handle the cooldown. </summary>
|
|
protected IEnumerator CooldownRoutine()
|
|
{
|
|
if (DisableWhenInCooldown) interactable = false;
|
|
yield return new WaitForEndOfFrame(); //mark as in cooldown after the first frame to avoid race conditions with UIBehaviours
|
|
inCooldown = true;
|
|
yield return new WaitForSecondsRealtime(Cooldown);
|
|
if (DisableWhenInCooldown) interactable = true;
|
|
inCooldown = false;
|
|
cooldownRoutine = null;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|