summaryrefslogtreecommitdiff
path: root/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs')
-rw-r--r--Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs672
1 files changed, 672 insertions, 0 deletions
diff --git a/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs
new file mode 100644
index 0000000..9cc6178
--- /dev/null
+++ b/Assets/uGUI-2017.1/UnityEngine.UI/UI/Core/UIControls/Selectable.cs
@@ -0,0 +1,672 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine.Serialization;
+using UnityEngine.EventSystems;
+
+namespace UnityEngine.UI
+{
+ // Simple selectable object - derived from to create a control.
+ [AddComponentMenu("UI/Selectable", 70)]
+ [ExecuteInEditMode]
+ [SelectionBase]
+ [DisallowMultipleComponent]
+ public class Selectable
+ :
+ UIBehaviour,
+ IMoveHandler, // Input>Horizontal\Vertical移动时收到这个消息
+ IPointerDownHandler, IPointerUpHandler, // 点击click
+ IPointerEnterHandler, IPointerExitHandler, // 悬停hover
+ ISelectHandler, IDeselectHandler // navigation获得焦点时收到这个消息
+ {
+ // Selection state
+
+ // 当前场景中的Selectable组件
+ // List of all the selectable objects currently active in the scene
+ private static List<Selectable> s_List = new List<Selectable>();
+ public static List<Selectable> allSelectables { get { return s_List; } }
+
+ // Navigation information.
+ [FormerlySerializedAs("navigation")]
+ [SerializeField]
+ private Navigation m_Navigation = Navigation.defaultNavigation;
+
+ // Highlighting state
+ public enum Transition
+ {
+ None,
+ ColorTint,
+ SpriteSwap,
+ Animation
+ }
+
+ // Type of the transition that occurs when the button state changes.
+ [FormerlySerializedAs("transition")]
+ [SerializeField]
+ private Transition m_Transition = Transition.ColorTint;
+
+ // Colors used for a color tint-based transition.
+ [FormerlySerializedAs("colors")]
+ [SerializeField]
+ private ColorBlock m_Colors = ColorBlock.defaultColorBlock;
+
+ // Sprites used for a Image swap-based transition.
+ [FormerlySerializedAs("spriteState")]
+ [SerializeField]
+ private SpriteState m_SpriteState;
+
+ [FormerlySerializedAs("animationTriggers")]
+ [SerializeField]
+ private AnimationTriggers m_AnimationTriggers = new AnimationTriggers();
+
+ [Tooltip("Can the Selectable be interacted with?")]
+ [SerializeField]
+ private bool m_Interactable = true;
+
+ //c m_TargetGraphic 只是用来做展示,这个变量不会用来进行射线检测,在Awake()里注释掉也还会触发点击事件
+ // Graphic that will be colored.
+ [FormerlySerializedAs("highlightGraphic")]
+ [FormerlySerializedAs("m_HighlightGraphic")]
+ [SerializeField]
+ private Graphic m_TargetGraphic;
+
+
+ private bool m_GroupsAllowInteraction = true;
+
+ private SelectionState m_CurrentSelectionState;
+
+ public Navigation navigation { get { return m_Navigation; } set { if (SetPropertyUtility.SetStruct(ref m_Navigation, value)) OnSetProperty(); } }
+ public Transition transition { get { return m_Transition; } set { if (SetPropertyUtility.SetStruct(ref m_Transition, value)) OnSetProperty(); } }
+ public ColorBlock colors { get { return m_Colors; } set { if (SetPropertyUtility.SetStruct(ref m_Colors, value)) OnSetProperty(); } }
+ public SpriteState spriteState { get { return m_SpriteState; } set { if (SetPropertyUtility.SetStruct(ref m_SpriteState, value)) OnSetProperty(); } }
+ public AnimationTriggers animationTriggers { get { return m_AnimationTriggers; } set { if (SetPropertyUtility.SetClass(ref m_AnimationTriggers, value)) OnSetProperty(); } }
+ public Graphic targetGraphic {
+ get {
+ return m_TargetGraphic;
+ }
+ set {
+ if (SetPropertyUtility.SetClass(ref m_TargetGraphic, value))
+ OnSetProperty();
+ }
+ }
+ public bool interactable
+ {
+ get { return m_Interactable; }
+ set
+ {
+ if (SetPropertyUtility.SetStruct(ref m_Interactable, value))
+ {
+ if (!m_Interactable && EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
+ EventSystem.current.SetSelectedGameObject(null);
+ if (m_Interactable)
+ UpdateSelectionState(null);
+ OnSetProperty();
+ }
+ }
+ }
+
+ private bool isPointerInside { get; set; }
+ private bool isPointerDown { get; set; }
+ private bool hasSelection { get; set; }
+
+ protected Selectable()
+ {}
+
+ // Convenience function that converts the Graphic to a Image, if possible
+ public Image image
+ {
+ get {
+ return m_TargetGraphic as Image;
+ }
+ set {
+ m_TargetGraphic = value;
+ }
+ }
+
+ // Get the animator
+ public Animator animator
+ {
+ get { return GetComponent<Animator>(); }
+ }
+
+ protected override void Awake()
+ {
+ if (m_TargetGraphic == null)
+ m_TargetGraphic = GetComponent<Graphic>();
+ }
+
+ private readonly List<CanvasGroup> m_CanvasGroupCache = new List<CanvasGroup>();
+ protected override void OnCanvasGroupChanged()
+ {
+ // Figure out if parent groups allow interaction
+ // If no interaction is alowed... then we need
+ // to not do that :)
+ var groupAllowInteraction = true;
+ Transform t = transform;
+ while (t != null)
+ {
+ t.GetComponents(m_CanvasGroupCache);
+ bool shouldBreak = false;
+ for (var i = 0; i < m_CanvasGroupCache.Count; i++)
+ {
+ // if the parent group does not allow interaction
+ // we need to break
+ if (!m_CanvasGroupCache[i].interactable)
+ {
+ groupAllowInteraction = false;
+ shouldBreak = true;
+ }
+ // if this is a 'fresh' group, then break
+ // as we should not consider parents
+ if (m_CanvasGroupCache[i].ignoreParentGroups)
+ shouldBreak = true;
+ }
+ if (shouldBreak)
+ break;
+
+ t = t.parent;
+ }
+
+ if (groupAllowInteraction != m_GroupsAllowInteraction)
+ {
+ m_GroupsAllowInteraction = groupAllowInteraction;
+ OnSetProperty();
+ }
+ }
+
+ public virtual bool IsInteractable()
+ {
+ return m_GroupsAllowInteraction && m_Interactable;
+ }
+
+ // Call from unity if animation properties have changed
+ protected override void OnDidApplyAnimationProperties()
+ {
+ OnSetProperty();
+ }
+
+ // Select on enable and add to the list.
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+
+ s_List.Add(this);
+ var state = SelectionState.Normal;
+
+ // The button will be highlighted even in some cases where it shouldn't.
+ // For example: We only want to set the State as Highlighted if the StandaloneInputModule.m_CurrentInputMode == InputMode.Buttons
+ // But we dont have access to this, and it might not apply to other InputModules.
+ // TODO: figure out how to solve this. Case 617348.
+ if (hasSelection)
+ state = SelectionState.Highlighted;
+
+ m_CurrentSelectionState = state;
+ InternalEvaluateAndTransitionToSelectionState(true);
+ }
+
+ private void OnSetProperty()
+ {
+#if UNITY_EDITOR
+ if (!Application.isPlaying)
+ InternalEvaluateAndTransitionToSelectionState(true);
+ else
+#endif
+ InternalEvaluateAndTransitionToSelectionState(false);
+ }
+
+ // Remove from the list.
+ protected override void OnDisable()
+ {
+ s_List.Remove(this);
+ InstantClearState();
+ base.OnDisable();
+ }
+
+#if UNITY_EDITOR
+ protected override void OnValidate()
+ {
+ base.OnValidate();
+ m_Colors.fadeDuration = Mathf.Max(m_Colors.fadeDuration, 0.0f);
+
+ // OnValidate can be called before OnEnable, this makes it unsafe to access other components
+ // since they might not have been initialized yet.
+ // OnSetProperty potentially access Animator or Graphics. (case 618186)
+ if (isActiveAndEnabled)
+ {
+ if (!interactable && EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
+ EventSystem.current.SetSelectedGameObject(null);
+ // Need to clear out the override image on the target...
+ DoSpriteSwap(null);
+
+ // If the transition mode got changed, we need to clear all the transitions, since we don't know what the old transition mode was.
+ StartColorTween(Color.white, true);
+ TriggerAnimation(m_AnimationTriggers.normalTrigger);
+
+ // And now go to the right state.
+ InternalEvaluateAndTransitionToSelectionState(true);
+ }
+ }
+
+ protected override void Reset()
+ {
+ m_TargetGraphic = GetComponent<Graphic>();
+ }
+
+#endif // if UNITY_EDITOR
+
+ protected SelectionState currentSelectionState
+ {
+ get { return m_CurrentSelectionState; }
+ }
+
+ protected virtual void InstantClearState()
+ {
+ string triggerName = m_AnimationTriggers.normalTrigger;
+
+ isPointerInside = false;
+ isPointerDown = false;
+ hasSelection = false;
+
+ switch (m_Transition)
+ {
+ case Transition.ColorTint:
+ StartColorTween(Color.white, true);
+ break;
+ case Transition.SpriteSwap:
+ DoSpriteSwap(null);
+ break;
+ case Transition.Animation:
+ TriggerAnimation(triggerName);
+ break;
+ }
+ }
+
+ protected virtual void DoStateTransition(SelectionState state, bool instant)
+ {
+ Color tintColor;
+ Sprite transitionSprite;
+ string triggerName;
+
+ switch (state)
+ {
+ case SelectionState.Normal:
+ tintColor = m_Colors.normalColor;
+ transitionSprite = null;
+ triggerName = m_AnimationTriggers.normalTrigger;
+ break;
+ case SelectionState.Highlighted:
+ tintColor = m_Colors.highlightedColor;
+ transitionSprite = m_SpriteState.highlightedSprite;
+ triggerName = m_AnimationTriggers.highlightedTrigger;
+ break;
+ case SelectionState.Pressed:
+ tintColor = m_Colors.pressedColor;
+ transitionSprite = m_SpriteState.pressedSprite;
+ triggerName = m_AnimationTriggers.pressedTrigger;
+ break;
+ case SelectionState.Disabled:
+ tintColor = m_Colors.disabledColor;
+ transitionSprite = m_SpriteState.disabledSprite;
+ triggerName = m_AnimationTriggers.disabledTrigger;
+ break;
+ default:
+ tintColor = Color.black;
+ transitionSprite = null;
+ triggerName = string.Empty;
+ break;
+ }
+
+ if (gameObject.activeInHierarchy)
+ {
+ switch (m_Transition)
+ {
+ case Transition.ColorTint:
+ StartColorTween(tintColor * m_Colors.colorMultiplier, instant);
+ break;
+ case Transition.SpriteSwap:
+ DoSpriteSwap(transitionSprite);
+ break;
+ case Transition.Animation:
+ TriggerAnimation(triggerName);
+ break;
+ }
+ }
+ }
+
+ protected enum SelectionState
+ {
+ Normal,
+ Highlighted,
+ Pressed,
+ Disabled
+ }
+
+ // Selection logic
+
+ // Find the next selectable object in the specified world-space direction.
+ public Selectable FindSelectable(Vector3 dir)
+ {
+ dir = dir.normalized;
+ Vector3 localDir = Quaternion.Inverse(transform.rotation) * dir;
+ Vector3 pos = transform.TransformPoint(GetPointOnRectEdge(transform as RectTransform, localDir));
+ float maxScore = Mathf.NegativeInfinity;
+ Selectable bestPick = null;
+ for (int i = 0; i < s_List.Count; ++i) // 遍历当前场景中的所有selectable组件
+ {
+ Selectable sel = s_List[i];
+
+ if (sel == this || sel == null)
+ continue;
+
+ if (!sel.IsInteractable() || sel.navigation.mode == Navigation.Mode.None)
+ continue;
+
+ var selRect = sel.transform as RectTransform;
+ Vector3 selCenter = selRect != null ? (Vector3)selRect.rect.center : Vector3.zero;
+ Vector3 myVector = sel.transform.TransformPoint(selCenter) - pos;
+
+ // Value that is the distance out along the direction.
+ float dot = Vector3.Dot(dir, myVector);
+
+ // Skip elements that are in the wrong direction or which have zero distance.
+ // This also ensures that the scoring formula below will not have a division by zero error.
+ if (dot <= 0)
+ continue;
+
+ // This scoring function has two priorities:
+ // - Score higher for positions that are closer.
+ // - Score higher for positions that are located in the right direction.
+ // This scoring function combines both of these criteria.
+ // It can be seen as this:
+ // Dot (dir, myVector.normalized) / myVector.magnitude
+ // The first part equals 1 if the direction of myVector is the same as dir, and 0 if it's orthogonal.
+ // The second part scores lower the greater the distance is by dividing by the distance.
+ // The formula below is equivalent but more optimized.
+ //
+ // If a given score is chosen, the positions that evaluate to that score will form a circle
+ // that touches pos and whose center is located along dir. A way to visualize the resulting functionality is this:
+ // From the position pos, blow up a circular balloon so it grows in the direction of dir.
+ // The first Selectable whose center the circular balloon touches is the one that's chosen.
+ float score = dot / myVector.sqrMagnitude;
+
+ if (score > maxScore)
+ {
+ maxScore = score;
+ bestPick = sel;
+ }
+ }
+ return bestPick;
+ }
+
+ private static Vector3 GetPointOnRectEdge(RectTransform rect, Vector2 dir)
+ {
+ if (rect == null)
+ return Vector3.zero;
+ if (dir != Vector2.zero)
+ dir /= Mathf.Max(Mathf.Abs(dir.x), Mathf.Abs(dir.y));
+ dir = rect.rect.center + Vector2.Scale(rect.rect.size, dir * 0.5f);
+ return dir;
+ }
+
+ // Convenience function -- change the selection to the specified object if it's not null and happens to be active.
+ void Navigate(AxisEventData eventData, Selectable sel)
+ {
+ if (sel != null && sel.IsActive())
+ eventData.selectedObject = sel.gameObject; // 会发送一个selectHandler事件
+ }
+
+ // Find the selectable object to the left of this one.
+ public virtual Selectable FindSelectableOnLeft()
+ {
+ if (m_Navigation.mode == Navigation.Mode.Explicit)
+ {
+ return m_Navigation.selectOnLeft;
+ }
+ if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0)
+ {
+ return FindSelectable(transform.rotation * Vector3.left);
+ }
+ return null;
+ }
+
+ // Find the selectable object to the right of this one.
+ public virtual Selectable FindSelectableOnRight()
+ {
+ if (m_Navigation.mode == Navigation.Mode.Explicit)
+ {
+ return m_Navigation.selectOnRight;
+ }
+ if ((m_Navigation.mode & Navigation.Mode.Horizontal) != 0)
+ {
+ return FindSelectable(transform.rotation * Vector3.right);
+ }
+ return null;
+ }
+
+ // Find the selectable object above this one
+ public virtual Selectable FindSelectableOnUp()
+ {
+ if (m_Navigation.mode == Navigation.Mode.Explicit)
+ {
+ return m_Navigation.selectOnUp;
+ }
+ if ((m_Navigation.mode & Navigation.Mode.Vertical) != 0)
+ {
+ return FindSelectable(transform.rotation * Vector3.up);
+ }
+ return null;
+ }
+
+ // Find the selectable object below this one.
+ public virtual Selectable FindSelectableOnDown()
+ {
+ if (m_Navigation.mode == Navigation.Mode.Explicit)
+ {
+ return m_Navigation.selectOnDown;
+ }
+ if ((m_Navigation.mode & Navigation.Mode.Vertical) != 0)
+ {
+ return FindSelectable(transform.rotation * Vector3.down);
+ }
+ return null;
+ }
+
+ // input>horizontal\vertical产生这个事件,
+ public virtual void OnMove(AxisEventData eventData)
+ {
+ LogHelper.Log("OnMove() " + gameObject.name );
+
+ switch (eventData.moveDir)
+ {
+ case MoveDirection.Right:
+ Navigate(eventData, FindSelectableOnRight()); // 会向下一个selectableObj发送一个OnSelect消息
+ break;
+
+ case MoveDirection.Up:
+ Navigate(eventData, FindSelectableOnUp());
+ break;
+
+ case MoveDirection.Left:
+ Navigate(eventData, FindSelectableOnLeft());
+ break;
+
+ case MoveDirection.Down:
+ Navigate(eventData, FindSelectableOnDown());
+ break;
+ }
+ }
+
+ void StartColorTween(Color targetColor, bool instant)
+ {
+ if (m_TargetGraphic == null)
+ return;
+
+ // 起一个协程做tween动画
+ m_TargetGraphic.CrossFadeColor(targetColor, instant ? 0f : m_Colors.fadeDuration, true, true);
+ }
+
+ void DoSpriteSwap(Sprite newSprite)
+ {
+ if (image == null)
+ return;
+
+ image.overrideSprite = newSprite;
+ }
+
+ void TriggerAnimation(string triggername)
+ {
+ if (transition != Transition.Animation || animator == null || !animator.isActiveAndEnabled || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername))
+ return;
+
+ LogHelper.Log("trigger animation");
+
+ animator.ResetTrigger(m_AnimationTriggers.normalTrigger);
+ animator.ResetTrigger(m_AnimationTriggers.pressedTrigger);
+ animator.ResetTrigger(m_AnimationTriggers.highlightedTrigger);
+ animator.ResetTrigger(m_AnimationTriggers.disabledTrigger);
+
+ animator.SetTrigger(triggername);
+ }
+
+ // Whether the control should be 'selected'.
+ protected bool IsHighlighted(BaseEventData eventData)
+ {
+ if (!IsActive())
+ return false;
+
+ if (IsPressed())
+ return false;
+
+ bool selected = hasSelection;
+ if (eventData is PointerEventData)
+ {
+ var pointerData = eventData as PointerEventData;
+ selected |=
+ (isPointerDown && !isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer moved off
+ || (!isPointerDown && isPointerInside && pointerData.pointerPress == gameObject) // This object pressed, but pointer released over (PointerUp event)
+ || (!isPointerDown && isPointerInside && pointerData.pointerPress == null); // Nothing pressed, but pointer is over
+ }
+ else
+ {
+ selected |= isPointerInside;
+ }
+ return selected;
+ }
+
+ [Obsolete("Is Pressed no longer requires eventData", false)]
+ protected bool IsPressed(BaseEventData eventData)
+ {
+ return IsPressed();
+ }
+
+ // Whether the control should be pressed.
+ protected bool IsPressed()
+ {
+ if (!IsActive())
+ return false;
+
+ return isPointerInside && isPointerDown;
+ }
+
+ // The current visual state of the control.
+ protected void UpdateSelectionState(BaseEventData eventData)
+ {
+ if (IsPressed())
+ {
+ m_CurrentSelectionState = SelectionState.Pressed;
+ return;
+ }
+
+ if (IsHighlighted(eventData))
+ {
+ m_CurrentSelectionState = SelectionState.Highlighted;
+ return;
+ }
+
+ m_CurrentSelectionState = SelectionState.Normal;
+ }
+
+ // 更新状态播放动画
+ // Change the button to the correct state
+ private void EvaluateAndTransitionToSelectionState(BaseEventData eventData)
+ {
+ if (!IsActive() || !IsInteractable())
+ return;
+
+ UpdateSelectionState(eventData);
+ InternalEvaluateAndTransitionToSelectionState(false);
+ }
+
+ private void InternalEvaluateAndTransitionToSelectionState(bool instant)
+ {
+ var transitionState = m_CurrentSelectionState;
+ if (IsActive() && !IsInteractable())
+ transitionState = SelectionState.Disabled;
+ DoStateTransition(transitionState, instant);
+ }
+
+ public virtual void OnPointerDown(PointerEventData eventData)
+ {
+ LogHelper.Log("OnPointerDown() "+ gameObject.name);
+
+ if (eventData.button != PointerEventData.InputButton.Left)
+ return;
+
+ // Selection tracking
+ if (IsInteractable() && navigation.mode != Navigation.Mode.None && EventSystem.current != null)
+ EventSystem.current.SetSelectedGameObject(gameObject, eventData); // 选中这个UI组件
+
+ isPointerDown = true;
+ EvaluateAndTransitionToSelectionState(eventData);
+ }
+
+ public virtual void OnPointerUp(PointerEventData eventData)
+ {
+ LogHelper.Log("OnPointerUp() " + gameObject.name);
+
+ if (eventData.button != PointerEventData.InputButton.Left)
+ return;
+
+ isPointerDown = false;
+ EvaluateAndTransitionToSelectionState(eventData);
+ }
+
+ public virtual void OnPointerEnter(PointerEventData eventData)
+ {
+ LogHelper.Log("OnPointerEnter() " + gameObject.name);
+
+ isPointerInside = true;
+ EvaluateAndTransitionToSelectionState(eventData);
+ }
+
+ public virtual void OnPointerExit(PointerEventData eventData)
+ {
+ LogHelper.Log("OnPointerExit() " + gameObject.name);
+
+ isPointerInside = false;
+ EvaluateAndTransitionToSelectionState(eventData);
+ }
+
+ // 被选中
+ public virtual void OnSelect(BaseEventData eventData)
+ {
+ LogHelper.Log("OnSelect() " + gameObject.name);
+ hasSelection = true;
+ EvaluateAndTransitionToSelectionState(eventData);
+ }
+
+ public virtual void OnDeselect(BaseEventData eventData)
+ {
+ LogHelper.Log("OnDeselect() " + gameObject.name);
+ hasSelection = false;
+ EvaluateAndTransitionToSelectionState(eventData);
+ }
+
+ public virtual void Select()
+ {
+ if (EventSystem.current == null || EventSystem.current.alreadySelecting)
+ return;
+
+ EventSystem.current.SetSelectedGameObject(gameObject);
+ }
+ }
+}