using System.Collections.Generic; using System.Text; using UnityEngine.UI; namespace UnityEngine.EventSystems { /// /// A BaseInputModule for pointer input. /// public abstract class PointerInputModule : BaseInputModule { /// /// Id of the cached left mouse pointer event. /// public const int kMouseLeftId = -1; /// /// Id of the cached right mouse pointer event. /// public const int kMouseRightId = -2; /// /// Id of the cached middle mouse pointer event. /// public const int kMouseMiddleId = -3; /// /// Touch id for when simulating touches on a non touch device. /// public const int kFakeTouchesId = -4; protected Dictionary m_PointerData = new Dictionary(); /// /// Search the cache for currently active pointers, return true if found. /// /// Touch ID /// Found data /// If not found should it be created /// True if pointer is found. protected bool GetPointerData(int id, out PointerEventData data, bool create) { if (!m_PointerData.TryGetValue(id, out data) && create) { data = new PointerEventData(eventSystem) { pointerId = id, }; m_PointerData.Add(id, data); return true; } return false; } /// /// Remove the PointerEventData from the cache. /// protected void RemovePointerData(PointerEventData data) { m_PointerData.Remove(data.pointerId); } /// /// Given a touch populate the PointerEventData and return if we are pressed or released. /// /// Touch being processed /// Are we pressed this frame /// Are we released this frame /// protected PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released) { PointerEventData pointerData; var created = GetPointerData(input.fingerId, out pointerData, true); pointerData.Reset(); pressed = created || (input.phase == TouchPhase.Began); released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended); if (created) pointerData.position = input.position; if (pressed) pointerData.delta = Vector2.zero; else pointerData.delta = input.position - pointerData.position; pointerData.position = input.position; pointerData.button = PointerEventData.InputButton.Left; if (input.phase == TouchPhase.Canceled) { pointerData.pointerCurrentRaycast = new RaycastResult(); } else { eventSystem.RaycastAll(pointerData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); pointerData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); } pointerData.pressure = input.pressure; pointerData.altitudeAngle = input.altitudeAngle; pointerData.azimuthAngle = input.azimuthAngle; pointerData.radius = Vector2.one * input.radius; pointerData.radiusVariance = Vector2.one * input.radiusVariance; return pointerData; } /// /// Copy one PointerEventData to another. /// protected void CopyFromTo(PointerEventData @from, PointerEventData @to) { @to.position = @from.position; @to.delta = @from.delta; @to.scrollDelta = @from.scrollDelta; @to.pointerCurrentRaycast = @from.pointerCurrentRaycast; @to.pointerEnter = @from.pointerEnter; @to.pressure = @from.pressure; @to.tangentialPressure = @from.tangentialPressure; @to.altitudeAngle = @from.altitudeAngle; @to.azimuthAngle = @from.azimuthAngle; @to.twist = @from.twist; @to.radius = @from.radius; @to.radiusVariance = @from.radiusVariance; } /// /// Given a mouse button return the current state for the frame. /// /// Mouse button ID protected PointerEventData.FramePressState StateForMouseButton(int buttonId) { var pressed = input.GetMouseButtonDown(buttonId); var released = input.GetMouseButtonUp(buttonId); if (pressed && released) return PointerEventData.FramePressState.PressedAndReleased; if (pressed) return PointerEventData.FramePressState.Pressed; if (released) return PointerEventData.FramePressState.Released; return PointerEventData.FramePressState.NotChanged; } protected class ButtonState { private PointerEventData.InputButton m_Button = PointerEventData.InputButton.Left; public MouseButtonEventData eventData { get { return m_EventData; } set { m_EventData = value; } } public PointerEventData.InputButton button { get { return m_Button; } set { m_Button = value; } } private MouseButtonEventData m_EventData; } protected class MouseState { private List m_TrackedButtons = new List(); public bool AnyPressesThisFrame() { var trackedButtonsCount = m_TrackedButtons.Count; for (int i = 0; i < trackedButtonsCount; i++) { if (m_TrackedButtons[i].eventData.PressedThisFrame()) return true; } return false; } public bool AnyReleasesThisFrame() { var trackedButtonsCount = m_TrackedButtons.Count; for (int i = 0; i < trackedButtonsCount; i++) { if (m_TrackedButtons[i].eventData.ReleasedThisFrame()) return true; } return false; } public ButtonState GetButtonState(PointerEventData.InputButton button) { ButtonState tracked = null; var trackedButtonsCount = m_TrackedButtons.Count; for (int i = 0; i < trackedButtonsCount; i++) { if (m_TrackedButtons[i].button == button) { tracked = m_TrackedButtons[i]; break; } } if (tracked == null) { tracked = new ButtonState { button = button, eventData = new MouseButtonEventData() }; m_TrackedButtons.Add(tracked); } return tracked; } public void SetButtonState(PointerEventData.InputButton button, PointerEventData.FramePressState stateForMouseButton, PointerEventData data) { var toModify = GetButtonState(button); toModify.eventData.buttonState = stateForMouseButton; toModify.eventData.buttonData = data; } } /// /// Information about a mouse button event. /// public class MouseButtonEventData { /// /// The state of the button this frame. /// public PointerEventData.FramePressState buttonState; /// /// Pointer data associated with the mouse event. /// public PointerEventData buttonData; /// /// Was the button pressed this frame? /// public bool PressedThisFrame() { return buttonState == PointerEventData.FramePressState.Pressed || buttonState == PointerEventData.FramePressState.PressedAndReleased; } /// /// Was the button released this frame? /// public bool ReleasedThisFrame() { return buttonState == PointerEventData.FramePressState.Released || buttonState == PointerEventData.FramePressState.PressedAndReleased; } } private readonly MouseState m_MouseState = new MouseState(); /// /// Return the current MouseState. Using the default pointer. /// protected virtual MouseState GetMousePointerEventData() { return GetMousePointerEventData(0); } /// /// Return the current MouseState. /// protected virtual MouseState GetMousePointerEventData(int id) { // Populate the left button... PointerEventData leftData; var created = GetPointerData(kMouseLeftId, out leftData, true); leftData.Reset(); if (created) leftData.position = input.mousePosition; Vector2 pos = input.mousePosition; if (Cursor.lockState == CursorLockMode.Locked) { // We don't want to do ANY cursor-based interaction when the mouse is locked leftData.position = new Vector2(-1.0f, -1.0f); leftData.delta = Vector2.zero; } else { leftData.delta = pos - leftData.position; leftData.position = pos; } leftData.scrollDelta = input.mouseScrollDelta; leftData.button = PointerEventData.InputButton.Left; eventSystem.RaycastAll(leftData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); leftData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); // copy the apropriate data into right and middle slots PointerEventData rightData; GetPointerData(kMouseRightId, out rightData, true); rightData.Reset(); CopyFromTo(leftData, rightData); rightData.button = PointerEventData.InputButton.Right; PointerEventData middleData; GetPointerData(kMouseMiddleId, out middleData, true); middleData.Reset(); CopyFromTo(leftData, middleData); middleData.button = PointerEventData.InputButton.Middle; m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData); m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData); m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData); return m_MouseState; } /// /// Return the last PointerEventData for the given touch / mouse id. /// protected PointerEventData GetLastPointerEventData(int id) { PointerEventData data; GetPointerData(id, out data, false); return data; } private static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold) { if (!useDragThreshold) return true; return (pressPos - currentPos).sqrMagnitude >= threshold * threshold; } /// /// Process movement for the current frame with the given pointer event. /// protected virtual void ProcessMove(PointerEventData pointerEvent) { var targetGO = (Cursor.lockState == CursorLockMode.Locked ? null : pointerEvent.pointerCurrentRaycast.gameObject); HandlePointerExitAndEnter(pointerEvent, targetGO); } /// /// Process the drag for the current frame with the given pointer event. /// protected virtual void ProcessDrag(PointerEventData pointerEvent) { if (!pointerEvent.IsPointerMoving() || Cursor.lockState == CursorLockMode.Locked || pointerEvent.pointerDrag == null) return; if (!pointerEvent.dragging && ShouldStartDrag(pointerEvent.pressPosition, pointerEvent.position, eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold)) { ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.beginDragHandler); pointerEvent.dragging = true; } // Drag notification if (pointerEvent.dragging) { // Before doing drag we should cancel any pointer down state // And clear selection! if (pointerEvent.pointerPress != pointerEvent.pointerDrag) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; } ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.dragHandler); } } public override bool IsPointerOverGameObject(int pointerId) { var lastPointer = GetLastPointerEventData(pointerId); if (lastPointer != null) return lastPointer.pointerEnter != null; return false; } /// /// Clear all pointers and deselect any selected objects in the EventSystem. /// protected void ClearSelection() { var baseEventData = GetBaseEventData(); foreach (var pointer in m_PointerData.Values) { // clear all selection HandlePointerExitAndEnter(pointer, null); } m_PointerData.Clear(); eventSystem.SetSelectedGameObject(null, baseEventData); } public override string ToString() { var sb = new StringBuilder("Pointer Input Module of type: " + GetType()); sb.AppendLine(); foreach (var pointer in m_PointerData) { if (pointer.Value == null) continue; sb.AppendLine("Pointer: " + pointer.Key); sb.AppendLine(pointer.Value.ToString()); } return sb.ToString(); } /// /// Deselect the current selected GameObject if the currently pointed-at GameObject is different. /// /// The GameObject the pointer is currently over. /// Current event data. protected void DeselectIfSelectionChanged(GameObject currentOverGo, BaseEventData pointerEvent) { // Selection tracking var selectHandlerGO = ExecuteEvents.GetEventHandler(currentOverGo); // if we have clicked something new, deselect the old thing // leave 'selection handling' up to the press event though. if (selectHandlerGO != eventSystem.currentSelectedGameObject) eventSystem.SetSelectedGameObject(null, pointerEvent); } } }