namespace UIWidgets
{
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
///
/// SelectableHelper.
/// Allow to control additional Graphic component according selection state of current gameobject.
///
[RequireComponent(typeof(Selectable))]
[AddComponentMenu("UI/New UI Widgets/Helpers/Selectable Helper")]
[ExecuteInEditMode]
public class SelectableHelper : UIBehaviour,
IPointerDownHandler, IPointerUpHandler,
IPointerEnterHandler, IPointerExitHandler,
ISelectHandler, IDeselectHandler
{
///
/// Selection states.
///
public enum SelectionState
{
///
/// Normal.
///
Normal,
///
/// Highlighted.
///
Highlighted,
///
/// Pressed.
///
Pressed,
///
/// Disabled.
///
Disabled,
}
[SerializeField]
Selectable.Transition transition = Selectable.Transition.ColorTint;
[SerializeField]
ColorBlock colors = ColorBlock.defaultColorBlock;
[SerializeField]
SpriteState spriteState;
[SerializeField]
AnimationTriggers animationTriggers = new AnimationTriggers();
[SerializeField]
Graphic targetGraphic;
SelectionState currentSelectionState;
///
/// The type of transition that will be applied to the targetGraphic when the state changes.
///
/// The transition.
public Selectable.Transition Transition
{
get
{
return transition;
}
set
{
if (transition != value)
{
transition = value;
OnSetProperty();
}
}
}
///
/// The ColorBlock for this object.
///
/// The colors.
public ColorBlock Colors
{
get
{
return colors;
}
set
{
if (!colors.Equals(value))
{
colors = value;
OnSetProperty();
}
}
}
///
/// The SpriteState for this object.
///
/// The state of the sprite.
public SpriteState SpriteState
{
get
{
return spriteState;
}
set
{
if (!spriteState.Equals(value))
{
spriteState = value;
OnSetProperty();
}
}
}
///
/// The AnimationTriggers for this object.
///
/// The animation triggers.
public AnimationTriggers AnimationTriggers
{
get
{
return animationTriggers;
}
set
{
animationTriggers = value;
OnSetProperty();
}
}
///
/// Graphic that will be transitioned upon.
///
/// The target graphic.
public Graphic TargetGraphic
{
get
{
return targetGraphic;
}
set
{
targetGraphic = value;
OnSetProperty();
}
}
Selectable parent;
///
/// Selectable component.
///
/// The parent.
protected Selectable Parent
{
get
{
if (parent == null)
{
parent = GetComponent();
}
return parent;
}
}
///
/// Gets a value indicating whether this object is interactable.
///
/// true if interactable; otherwise, false.
public bool Interactable
{
get
{
return (Parent != null) && Parent.IsInteractable();
}
}
bool isPointerInside;
bool isPointerDown;
bool hasSelection;
///
/// Convenience function that converts the referenced Graphic to a Image, if possible.
///
/// The image.
public Image Image
{
get
{
return TargetGraphic as Image;
}
set
{
TargetGraphic = value;
}
}
///
/// Convenience function to get the Animator component on the GameObject.
///
/// The animator.
public Animator Animator
{
get
{
return GetComponent();
}
}
///
/// Awake this instance.
///
protected override void Awake()
{
if (TargetGraphic == null)
{
TargetGraphic = GetComponent();
}
}
///
/// Determines whether this instance is interactable.
///
/// true if this instance is interactable; otherwise, false.
public virtual bool IsInteractable()
{
return Interactable;
}
///
/// Callback for when properties have been changed by animation.
///
protected override void OnDidApplyAnimationProperties()
{
OnSetProperty();
}
///
/// This function is called when the object becomes enabled and active.
///
protected override void OnEnable()
{
base.OnEnable();
currentSelectionState = hasSelection ? SelectionState.Highlighted : SelectionState.Normal;
TransitionToSelectionState(true);
}
void OnSetProperty()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
TransitionToSelectionState(true);
}
else
#endif
{
TransitionToSelectionState(false);
}
}
///
/// This function is called when the behaviour becomes disabled or inactive.
///
protected override void OnDisable()
{
InstantClearState();
base.OnDisable();
}
#if UNITY_EDITOR
///
/// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
///
protected override void OnValidate()
{
base.OnValidate();
colors.fadeDuration = Mathf.Max(colors.fadeDuration, 0.0f);
if (IsActive())
{
SpriteChange(null);
ColorChange(Color.white, true);
AnimationChange(AnimationTriggers.normalTrigger);
TransitionToSelectionState(true);
}
}
///
/// Reset to default values.
///
protected override void Reset()
{
TargetGraphic = GetComponent();
}
#endif
///
/// Gets the current selection state.
///
/// The state current selection state.
public SelectionState CurrentSelectionState
{
get
{
return currentSelectionState;
}
}
///
/// Clear any internal state from the SelectableHelper (used when disabling).
///
protected virtual void InstantClearState()
{
string triggerName = AnimationTriggers.normalTrigger;
isPointerInside = false;
isPointerDown = false;
hasSelection = false;
switch (Transition)
{
case Selectable.Transition.ColorTint:
ColorChange(Color.white, true);
break;
case Selectable.Transition.SpriteSwap:
SpriteChange(null);
break;
case Selectable.Transition.Animation:
AnimationChange(triggerName);
break;
}
}
///
/// Transition the SelectableHelper to the entered state.
///
/// State.
/// If set to true instant.
protected virtual void DoStateTransition(SelectionState state, bool instant)
{
if (!gameObject.activeInHierarchy)
{
return;
}
switch (Transition)
{
case Selectable.Transition.ColorTint:
TransitionColor(state, instant);
break;
case Selectable.Transition.SpriteSwap:
TransitionSprite(state, instant);
break;
case Selectable.Transition.Animation:
TransitionAnimation(state, instant);
break;
}
}
///
/// Change color according selection state.
///
/// State.
/// If set to true instant.
protected void TransitionColor(SelectionState state, bool instant)
{
Color tintColor;
switch (state)
{
case SelectionState.Normal:
tintColor = Colors.normalColor;
break;
case SelectionState.Highlighted:
tintColor = Colors.highlightedColor;
break;
case SelectionState.Pressed:
tintColor = Colors.pressedColor;
break;
case SelectionState.Disabled:
tintColor = Colors.disabledColor;
break;
default:
tintColor = Color.black;
break;
}
ColorChange(tintColor * Colors.colorMultiplier, instant);
}
///
/// Change color.
///
/// Target color.
/// If set to true instant.
protected void ColorChange(Color targetColor, bool instant)
{
if (TargetGraphic == null)
{
return;
}
TargetGraphic.CrossFadeColor(targetColor, instant ? 0f : Colors.fadeDuration, true, true);
}
///
/// Change sprite according selection state.
///
/// State.
/// If set to true instant.
protected virtual void TransitionSprite(SelectionState state, bool instant)
{
switch (state)
{
case SelectionState.Normal:
SpriteChange(null);
break;
case SelectionState.Highlighted:
SpriteChange(SpriteState.highlightedSprite);
break;
case SelectionState.Pressed:
SpriteChange(SpriteState.pressedSprite);
break;
case SelectionState.Disabled:
SpriteChange(SpriteState.disabledSprite);
break;
default:
SpriteChange(null);
break;
}
}
///
/// Change sprite.
///
/// New sprite.
protected void SpriteChange(Sprite newSprite)
{
if (Image == null)
{
return;
}
Image.overrideSprite = newSprite;
}
///
/// Change animation according selection state.
///
/// State.
/// If set to true instant.
protected virtual void TransitionAnimation(SelectionState state, bool instant)
{
switch (state)
{
case SelectionState.Normal:
AnimationChange(AnimationTriggers.normalTrigger);
break;
case SelectionState.Highlighted:
AnimationChange(AnimationTriggers.highlightedTrigger);
break;
case SelectionState.Pressed:
AnimationChange(AnimationTriggers.pressedTrigger);
break;
case SelectionState.Disabled:
AnimationChange(AnimationTriggers.disabledTrigger);
break;
default:
AnimationChange(string.Empty);
break;
}
}
///
/// Run animation.
///
/// Triggername.
protected void AnimationChange(string triggername)
{
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
if (Animator == null || !Animator.isActiveAndEnabled || Animator.runtimeAnimatorController == null || string.IsNullOrEmpty(triggername))
#else
if (Animator == null || Animator.runtimeAnimatorController == null || string.IsNullOrEmpty(triggername))
#endif
{
return;
}
Animator.ResetTrigger(AnimationTriggers.normalTrigger);
Animator.ResetTrigger(AnimationTriggers.pressedTrigger);
Animator.ResetTrigger(AnimationTriggers.highlightedTrigger);
Animator.ResetTrigger(AnimationTriggers.disabledTrigger);
Animator.SetTrigger(triggername);
}
///
/// Is the selectable currently 'highlighted'.
///
/// true if selectable 'highlighted'; otherwise, false.
/// Event data.
protected bool IsHighlighted(BaseEventData eventData)
{
if (!IsActive())
{
return false;
}
if (IsPressed())
{
return false;
}
bool selected = hasSelection;
var pointerData = eventData as PointerEventData;
if (pointerData != null)
{
selected |=
(isPointerDown && !isPointerInside && pointerData.pointerPress == gameObject)
|| (!isPointerDown && isPointerInside && pointerData.pointerPress == gameObject)
|| (!isPointerDown && isPointerInside && pointerData.pointerPress == null);
}
else
{
selected |= isPointerInside;
}
return selected;
}
///
/// Is the selectable currently 'pressed'.
///
/// true if selectable pressed; otherwise, false.
protected bool IsPressed()
{
if (!IsActive())
{
return false;
}
return isPointerInside && isPointerDown;
}
///
/// Internally update the selection state of the Selectable.
///
/// Event data.
protected void UpdateSelectionState(BaseEventData eventData)
{
if (IsPressed())
{
currentSelectionState = SelectionState.Pressed;
return;
}
if (IsHighlighted(eventData))
{
currentSelectionState = SelectionState.Highlighted;
return;
}
currentSelectionState = SelectionState.Normal;
}
///
/// Evaluates the selection state and transition to it.
///
/// Event data.
void EvaluateAndTransitionToSelectionState(BaseEventData eventData)
{
if (!IsActive())
{
return;
}
UpdateSelectionState(eventData);
TransitionToSelectionState(false);
}
///
/// Transition to selection state.
///
/// If set to true instant.
void TransitionToSelectionState(bool instant)
{
var transitionState = (IsActive() && !IsInteractable()) ? SelectionState.Disabled : currentSelectionState;
DoStateTransition(transitionState, instant);
}
///
/// Evaluate current state and transition to pressed state.
///
/// Event data.
public virtual void OnPointerDown(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
isPointerDown = true;
EvaluateAndTransitionToSelectionState(eventData);
}
///
/// Evaluate eventData and transition to appropriate state.
///
/// Event data.
public virtual void OnPointerUp(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
isPointerDown = false;
EvaluateAndTransitionToSelectionState(eventData);
}
///
/// Evaluate current state and transition to appropriate state.
///
/// Event data.
public virtual void OnPointerEnter(PointerEventData eventData)
{
isPointerInside = true;
EvaluateAndTransitionToSelectionState(eventData);
}
///
/// Evaluate current state and transition to normal state.
///
/// Event data.
public virtual void OnPointerExit(PointerEventData eventData)
{
isPointerInside = false;
EvaluateAndTransitionToSelectionState(eventData);
}
///
/// Set selection and transition to appropriate state.
///
/// Event data.
public virtual void OnSelect(BaseEventData eventData)
{
hasSelection = true;
EvaluateAndTransitionToSelectionState(eventData);
}
///
/// Unset selection and transition to appropriate state.
///
/// Event data.
public virtual void OnDeselect(BaseEventData eventData)
{
hasSelection = false;
EvaluateAndTransitionToSelectionState(eventData);
}
}
}