471 lines
19 KiB
C#
471 lines
19 KiB
C#
#if MM_UI
|
|
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace MoreMountains.Tools
|
|
{
|
|
/// <summary>
|
|
/// Add this component to a GUI Image to have it act as a button.
|
|
/// Bind pressed down, pressed continually and released actions to it from the inspector
|
|
/// Handles mouse and multi touch
|
|
/// </summary>
|
|
[RequireComponent(typeof(Rect))]
|
|
[RequireComponent(typeof(CanvasGroup))]
|
|
[AddComponentMenu("More Mountains/Tools/Controls/MM Touch Button")]
|
|
public class MMTouchButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler,
|
|
IPointerEnterHandler, ISubmitHandler
|
|
{
|
|
/// The different possible states for the button :
|
|
/// Off (default idle state), ButtonDown (button pressed for the first time), ButtonPressed (button being pressed), ButtonUp (button being released), Disabled (unclickable but still present on screen)
|
|
/// ButtonDown and ButtonUp will only last one frame, the others will last however long you press them / disable them / do nothing
|
|
public enum ButtonStates
|
|
{
|
|
Off,
|
|
ButtonDown,
|
|
ButtonPressed,
|
|
ButtonUp,
|
|
Disabled
|
|
}
|
|
|
|
[Header("Interaction")]
|
|
/// whether or not this button can be interacted with
|
|
public bool Interactable = true;
|
|
|
|
[Header("Binding")]
|
|
/// The method(s) to call when the button gets pressed down
|
|
[Tooltip("The method(s) to call when the button gets pressed down")]
|
|
public UnityEvent ButtonPressedFirstTime;
|
|
|
|
/// The method(s) to call when the button gets released
|
|
[Tooltip("The method(s) to call when the button gets released")]
|
|
public UnityEvent ButtonReleased;
|
|
|
|
/// The method(s) to call while the button is being pressed
|
|
[Tooltip("The method(s) to call while the button is being pressed")]
|
|
public UnityEvent ButtonPressed;
|
|
|
|
[Header("Sprite Swap")]
|
|
[MMInformation(
|
|
"Here you can define, for disabled and pressed states, if you want a different sprite, and a different color.",
|
|
MMInformationAttribute.InformationType.Info, false)]
|
|
/// the sprite to use on the button when it's in the disabled state
|
|
[Tooltip("the sprite to use on the button when it's in the disabled state")]
|
|
public Sprite DisabledSprite;
|
|
|
|
/// whether or not to change color when the button is disabled
|
|
[Tooltip("whether or not to change color when the button is disabled")]
|
|
public bool DisabledChangeColor;
|
|
|
|
/// the color to use when the button is disabled
|
|
[Tooltip("the color to use when the button is disabled")] [MMCondition("DisabledChangeColor", true)]
|
|
public Color DisabledColor = Color.white;
|
|
|
|
/// the sprite to use on the button when it's in the pressed state
|
|
[Tooltip("the sprite to use on the button when it's in the pressed state")]
|
|
public Sprite PressedSprite;
|
|
|
|
/// whether or not to change the button color on press
|
|
[Tooltip("whether or not to change the button color on press")]
|
|
public bool PressedChangeColor;
|
|
|
|
/// the color to use when the button is pressed
|
|
[Tooltip("the color to use when the button is pressed")] [MMCondition("PressedChangeColor", true)]
|
|
public Color PressedColor = Color.white;
|
|
|
|
/// the sprite to use on the button when it's in the highlighted state
|
|
[Tooltip("the sprite to use on the button when it's in the highlighted state")]
|
|
public Sprite HighlightedSprite;
|
|
|
|
/// whether or not to change color when highlighting the button
|
|
[Tooltip("whether or not to change color when highlighting the button")]
|
|
public bool HighlightedChangeColor;
|
|
|
|
/// the color to use when the button is highlighted
|
|
[Tooltip("the color to use when the button is highlighted")] [MMCondition("HighlightedChangeColor", true)]
|
|
public Color HighlightedColor = Color.white;
|
|
|
|
[Header("Opacity")]
|
|
[MMInformation(
|
|
"Here you can set different opacities for the button when it's pressed, idle, or disabled. Useful for visual feedback.",
|
|
MMInformationAttribute.InformationType.Info, false)]
|
|
/// the new opacity to apply to the canvas group when the button is pressed
|
|
[Tooltip("the opacity to apply to the canvas group when the button is pressed")]
|
|
public float PressedOpacity = 1f;
|
|
|
|
/// the new opacity to apply to the canvas group when the button is idle
|
|
[Tooltip("the new opacity to apply to the canvas group when the button is idle")]
|
|
public float IdleOpacity = 1f;
|
|
|
|
/// the new opacity to apply to the canvas group when the button is disabled
|
|
[Tooltip("the new opacity to apply to the canvas group when the button is disabled")]
|
|
public float DisabledOpacity = 1f;
|
|
|
|
[Header("Delays")]
|
|
[MMInformation(
|
|
"Specify here the delays to apply when the button is pressed initially, and when it gets released. Usually you'll keep them at 0.",
|
|
MMInformationAttribute.InformationType.Info, false)]
|
|
/// the delay to apply to events when the button gets pressed for the first time
|
|
[Tooltip("the delay to apply to events when the button gets pressed for the first time")]
|
|
public float PressedFirstTimeDelay;
|
|
|
|
/// the delay to apply to events when the button gets released
|
|
[Tooltip("the delay to apply to events when the button gets released")]
|
|
public float ReleasedDelay;
|
|
|
|
[Header("Buffer")]
|
|
/// the duration (in seconds) after a press during which the button can't be pressed again
|
|
[Tooltip("the duration (in seconds) after a press during which the button can't be pressed again")]
|
|
public float BufferDuration;
|
|
|
|
[Header("Animation")]
|
|
[MMInformation("Here you can bind an animator, and specify animation parameter names for the various states.",
|
|
MMInformationAttribute.InformationType.Info, false)]
|
|
/// an animator you can bind to this button to have its states updated to reflect the button's states
|
|
[Tooltip("an animator you can bind to this button to have its states updated to reflect the button's states")]
|
|
public Animator Animator;
|
|
|
|
/// the name of the animation parameter to turn true when the button is idle
|
|
[Tooltip("the name of the animation parameter to turn true when the button is idle")]
|
|
public string IdleAnimationParameterName = "Idle";
|
|
|
|
/// the name of the animation parameter to turn true when the button is disabled
|
|
[Tooltip("the name of the animation parameter to turn true when the button is disabled")]
|
|
public string DisabledAnimationParameterName = "Disabled";
|
|
|
|
/// the name of the animation parameter to turn true when the button is pressed
|
|
[Tooltip("the name of the animation parameter to turn true when the button is pressed")]
|
|
public string PressedAnimationParameterName = "Pressed";
|
|
|
|
[Header("Mouse Mode")]
|
|
[MMInformation(
|
|
"If you set this to true, you'll need to actually press the button for it to be triggered, otherwise a simple hover will trigger it (better to leave it unchecked if you're going for touch input).",
|
|
MMInformationAttribute.InformationType.Info, false)]
|
|
/// If you set this to true, you'll need to actually press the button for it to be triggered, otherwise a simple hover will trigger it (better for touch input).
|
|
[Tooltip(
|
|
"If you set this to true, you'll need to actually press the button for it to be triggered, otherwise a simple hover will trigger it (better for touch input).")]
|
|
public bool MouseMode;
|
|
|
|
public bool PreventLeftClick;
|
|
public bool PreventMiddleClick = true;
|
|
public bool PreventRightClick = true;
|
|
protected Animator _animator;
|
|
protected CanvasGroup _canvasGroup;
|
|
protected Image _image;
|
|
protected Color _initialColor;
|
|
protected float _initialOpacity;
|
|
protected Sprite _initialSprite;
|
|
protected float _lastClickTimestamp;
|
|
protected Selectable _selectable;
|
|
|
|
protected bool _zonePressed = false;
|
|
|
|
public virtual bool ReturnToInitialSpriteAutomatically { get; set; }
|
|
|
|
/// the current state of the button (off, down, pressed or up)
|
|
public virtual ButtonStates CurrentState { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// On Start, we get our canvasgroup and set our initial alpha
|
|
/// </summary>
|
|
protected virtual void Awake()
|
|
{
|
|
Initialization();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Every frame, if the touch zone is pressed, we trigger the OnPointerPressed method, to detect continuous press
|
|
/// </summary>
|
|
protected virtual void Update()
|
|
{
|
|
switch (CurrentState)
|
|
{
|
|
case ButtonStates.Off:
|
|
SetOpacity(IdleOpacity);
|
|
if (_image != null && ReturnToInitialSpriteAutomatically)
|
|
{
|
|
_image.color = _initialColor;
|
|
_image.sprite = _initialSprite;
|
|
}
|
|
|
|
if (_selectable != null)
|
|
{
|
|
_selectable.interactable = true;
|
|
if (EventSystem.current.currentSelectedGameObject == gameObject)
|
|
{
|
|
if (_image != null && HighlightedChangeColor) _image.color = HighlightedColor;
|
|
if (HighlightedSprite != null) _image.sprite = HighlightedSprite;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case ButtonStates.Disabled:
|
|
SetOpacity(DisabledOpacity);
|
|
if (_image != null)
|
|
{
|
|
if (DisabledSprite != null) _image.sprite = DisabledSprite;
|
|
if (DisabledChangeColor) _image.color = DisabledColor;
|
|
}
|
|
|
|
if (_selectable != null) _selectable.interactable = false;
|
|
break;
|
|
|
|
case ButtonStates.ButtonDown:
|
|
|
|
break;
|
|
|
|
case ButtonStates.ButtonPressed:
|
|
SetOpacity(PressedOpacity);
|
|
OnPointerPressed();
|
|
if (_image != null)
|
|
{
|
|
if (PressedSprite != null) _image.sprite = PressedSprite;
|
|
if (PressedChangeColor) _image.color = PressedColor;
|
|
}
|
|
|
|
break;
|
|
|
|
case ButtonStates.ButtonUp:
|
|
|
|
break;
|
|
}
|
|
|
|
UpdateAnimatorStates();
|
|
}
|
|
|
|
/// <summary>
|
|
/// At the end of every frame, we change our button's state if needed
|
|
/// </summary>
|
|
protected virtual void LateUpdate()
|
|
{
|
|
if (CurrentState == ButtonStates.ButtonUp) CurrentState = ButtonStates.Off;
|
|
if (CurrentState == ButtonStates.ButtonDown) CurrentState = ButtonStates.ButtonPressed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// OnEnable, we reset our button state
|
|
/// </summary>
|
|
protected virtual void OnEnable()
|
|
{
|
|
ResetButton();
|
|
}
|
|
|
|
/// <summary>
|
|
/// On disable we reset our flags and disable the button
|
|
/// </summary>
|
|
private void OnDisable()
|
|
{
|
|
var wasActive = CurrentState != ButtonStates.Off && CurrentState != ButtonStates.Disabled &&
|
|
CurrentState != ButtonStates.ButtonUp;
|
|
DisableButton();
|
|
CurrentState = ButtonStates.Off;
|
|
if (wasActive)
|
|
{
|
|
InvokeButtonStateChange(PointerEventData.FramePressState.Released, null);
|
|
ButtonReleased?.Invoke();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the bound pointer down action
|
|
/// </summary>
|
|
public virtual void OnPointerDown(PointerEventData data)
|
|
{
|
|
if (!Interactable) return;
|
|
|
|
if (!AllowedClick(data)) return;
|
|
|
|
if (Time.unscaledTime - _lastClickTimestamp < BufferDuration) return;
|
|
|
|
if (CurrentState != ButtonStates.Off) return;
|
|
CurrentState = ButtonStates.ButtonDown;
|
|
_lastClickTimestamp = Time.unscaledTime;
|
|
InvokeButtonStateChange(PointerEventData.FramePressState.Pressed, data);
|
|
if (Time.timeScale != 0 && PressedFirstTimeDelay > 0)
|
|
Invoke("InvokePressedFirstTime", PressedFirstTimeDelay);
|
|
else
|
|
ButtonPressedFirstTime.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the bound pointer enter action when touch enters zone
|
|
/// </summary>
|
|
public virtual void OnPointerEnter(PointerEventData data)
|
|
{
|
|
if (!Interactable) return;
|
|
if (!AllowedClick(data)) return;
|
|
if (!MouseMode) OnPointerDown(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the bound pointer exit action when touch is out of zone
|
|
/// </summary>
|
|
public virtual void OnPointerExit(PointerEventData data)
|
|
{
|
|
if (!Interactable) return;
|
|
if (!AllowedClick(data)) return;
|
|
if (!MouseMode) OnPointerUp(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the bound pointer up action
|
|
/// </summary>
|
|
public virtual void OnPointerUp(PointerEventData data)
|
|
{
|
|
if (!Interactable) return;
|
|
if (!AllowedClick(data)) return;
|
|
if (CurrentState != ButtonStates.ButtonPressed && CurrentState != ButtonStates.ButtonDown) return;
|
|
|
|
CurrentState = ButtonStates.ButtonUp;
|
|
InvokeButtonStateChange(PointerEventData.FramePressState.Released, data);
|
|
if (Time.timeScale != 0 && ReleasedDelay > 0)
|
|
Invoke("InvokeReleased", ReleasedDelay);
|
|
else
|
|
ButtonReleased.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// On submit, raises the appropriate events
|
|
/// </summary>
|
|
/// <param name="eventData"></param>
|
|
public virtual void OnSubmit(BaseEventData eventData)
|
|
{
|
|
if (ButtonPressedFirstTime != null) ButtonPressedFirstTime.Invoke();
|
|
if (ButtonReleased != null) ButtonReleased.Invoke();
|
|
}
|
|
|
|
public event Action<PointerEventData.FramePressState, PointerEventData> ButtonStateChange;
|
|
|
|
/// <summary>
|
|
/// On init we grab our Image, Animator and CanvasGroup and set them up
|
|
/// </summary>
|
|
protected virtual void Initialization()
|
|
{
|
|
ReturnToInitialSpriteAutomatically = true;
|
|
|
|
_selectable = GetComponent<Selectable>();
|
|
|
|
_image = GetComponent<Image>();
|
|
if (_image != null)
|
|
{
|
|
_initialColor = _image.color;
|
|
_initialSprite = _image.sprite;
|
|
}
|
|
|
|
_animator = GetComponent<Animator>();
|
|
if (Animator != null) _animator = Animator;
|
|
|
|
_canvasGroup = GetComponent<CanvasGroup>();
|
|
if (_canvasGroup != null)
|
|
{
|
|
_initialOpacity = IdleOpacity;
|
|
_canvasGroup.alpha = _initialOpacity;
|
|
_initialOpacity = _canvasGroup.alpha;
|
|
}
|
|
|
|
ResetButton();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the ButtonStateChange event for the specified state
|
|
/// </summary>
|
|
/// <param name="newState"></param>
|
|
/// <param name="data"></param>
|
|
public virtual void InvokeButtonStateChange(PointerEventData.FramePressState newState, PointerEventData data)
|
|
{
|
|
ButtonStateChange?.Invoke(newState, data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether or not the specified click is allowed, if in mouse mode
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <returns></returns>
|
|
protected virtual bool AllowedClick(PointerEventData data)
|
|
{
|
|
if (!MouseMode) return true;
|
|
if (PreventLeftClick && data.button == PointerEventData.InputButton.Left) return false;
|
|
if (PreventMiddleClick && data.button == PointerEventData.InputButton.Middle) return false;
|
|
if (PreventRightClick && data.button == PointerEventData.InputButton.Right) return false;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the ButtonPressedFirstTime event
|
|
/// </summary>
|
|
protected virtual void InvokePressedFirstTime()
|
|
{
|
|
if (ButtonPressedFirstTime != null) ButtonPressedFirstTime.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invokes the ButtonReleased event
|
|
/// </summary>
|
|
protected virtual void InvokeReleased()
|
|
{
|
|
if (ButtonReleased != null) ButtonReleased.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers the bound pointer pressed action
|
|
/// </summary>
|
|
public virtual void OnPointerPressed()
|
|
{
|
|
if (!Interactable) return;
|
|
CurrentState = ButtonStates.ButtonPressed;
|
|
if (ButtonPressed != null) ButtonPressed.Invoke();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the button's state and opacity
|
|
/// </summary>
|
|
protected virtual void ResetButton()
|
|
{
|
|
SetOpacity(_initialOpacity);
|
|
CurrentState = ButtonStates.Off;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prevents the button from receiving touches
|
|
/// </summary>
|
|
public virtual void DisableButton()
|
|
{
|
|
CurrentState = ButtonStates.Disabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows the button to receive touches
|
|
/// </summary>
|
|
public virtual void EnableButton()
|
|
{
|
|
if (CurrentState == ButtonStates.Disabled) CurrentState = ButtonStates.Off;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the canvas group's opacity to the requested value
|
|
/// </summary>
|
|
/// <param name="newOpacity"></param>
|
|
protected virtual void SetOpacity(float newOpacity)
|
|
{
|
|
if (_canvasGroup != null) _canvasGroup.alpha = newOpacity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates animator states based on the current state of the button
|
|
/// </summary>
|
|
protected virtual void UpdateAnimatorStates()
|
|
{
|
|
if (_animator == null) return;
|
|
if (DisabledAnimationParameterName != null)
|
|
_animator.SetBool(DisabledAnimationParameterName, CurrentState == ButtonStates.Disabled);
|
|
if (PressedAnimationParameterName != null)
|
|
_animator.SetBool(PressedAnimationParameterName, CurrentState == ButtonStates.ButtonPressed);
|
|
if (IdleAnimationParameterName != null)
|
|
_animator.SetBool(IdleAnimationParameterName, CurrentState == ButtonStates.Off);
|
|
}
|
|
}
|
|
}
|
|
#endif |