diff options
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.cs | 672 | 
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); +        } +    } +} | 
