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); } } }