// Copyright (c) 2018 Augie R. Maddox, Guavaman Enterprises. All rights reserved. // Based on Unity StandaloneInputModule.cs // https://bitbucket.org/Unity-Technologies/ui/src // Heavily modified to add multiple pointer support, interchangeable touch and mouse input sources, and 128 buttons per mouse. #region Defines #if UNITY_2020 || UNITY_2021 || UNITY_2022 || UNITY_2023 || UNITY_6000 || UNITY_6000_0_OR_NEWER #define UNITY_2020_PLUS #endif #if UNITY_2019 || UNITY_2020_PLUS #define UNITY_2019_PLUS #endif #if UNITY_2018 || UNITY_2019_PLUS #define UNITY_2018_PLUS #endif #if UNITY_2017 || UNITY_2018_PLUS #define UNITY_2017_PLUS #endif #if UNITY_5 || UNITY_2017_PLUS #define UNITY_5_PLUS #endif #if UNITY_5_1 || UNITY_5_2 || UNITY_5_3_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_1_PLUS #endif #if UNITY_5_2 || UNITY_5_3_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_2_PLUS #endif #if UNITY_5_3_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_3_PLUS #endif #if UNITY_5_4_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_4_PLUS #endif #if UNITY_5_5_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_5_PLUS #endif #if UNITY_5_6_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_6_PLUS #endif #if UNITY_5_7_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_7_PLUS #endif #if UNITY_5_8_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_8_PLUS #endif #if UNITY_5_9_OR_NEWER || UNITY_2017_PLUS #define UNITY_5_9_PLUS #endif #pragma warning disable 0219 #pragma warning disable 0618 #pragma warning disable 0649 #endregion namespace Rewired.Integration.UnityUI { using System; using System.Text; using UnityEngine; using UnityEngine.EventSystems; using System.Collections.Generic; using Rewired.UI; // Content added for Rewired public abstract class RewiredPointerInputModule : BaseInputModule { public const int kMouseLeftId = -1; public const int kMouseRightId = -2; public const int kMouseMiddleId = -3; public const int kFakeTouchesId = -4; private const int customButtonsStartingId = int.MinValue + 128; private const int customButtonsMaxCount = 128; private const int customButtonsLastId = customButtonsStartingId + customButtonsMaxCount; private readonly List m_MouseInputSourcesList = new List(); private Dictionary[]> m_PlayerPointerData = new Dictionary[]>(); private ITouchInputSource m_UserDefaultTouchInputSource; private UnityInputSource __m_DefaultInputSource; private UnityInputSource defaultInputSource { get { return __m_DefaultInputSource != null ? __m_DefaultInputSource : __m_DefaultInputSource = new UnityInputSource(); } } private IMouseInputSource defaultMouseInputSource { get { return defaultInputSource; } } protected ITouchInputSource defaultTouchInputSource { get { return defaultInputSource; } } protected bool IsDefaultMouse(IMouseInputSource mouse) { return defaultMouseInputSource == mouse; } /// /// Gets or sets the input source used for mouse input. /// /// The Player Id that owns the mouse input source. /// The index of the mouse input source. If a Player owns more than one mouse input source, set this to the index. See to determine the number of mouse input sources that exist for a Player. public IMouseInputSource GetMouseInputSource(int playerId, int mouseIndex) { if(mouseIndex < 0) throw new ArgumentOutOfRangeException("mouseIndex"); if(m_MouseInputSourcesList.Count == 0 && IsDefaultPlayer(playerId)) return defaultMouseInputSource; // fall back to default if nothing set int count = m_MouseInputSourcesList.Count; int pointerCount = 0; for(int i = 0; i < count; i++) { IMouseInputSource source = m_MouseInputSourcesList[i]; if(Utils.UnityTools.IsNullOrDestroyed(source)) continue; if(source.playerId != playerId) continue; if(mouseIndex == pointerCount) return source; pointerCount++; } return null; } /// /// Removes the mouse input source. /// /// The mouse input source. public void RemoveMouseInputSource(IMouseInputSource source) { if(source == null) throw new ArgumentNullException("source"); // do not check if destroyed here so removal works on destroyed objects m_MouseInputSourcesList.Remove(source); } /// /// Adds the mouse input source. /// /// The mouse input source. public void AddMouseInputSource(IMouseInputSource source) { if(Utils.UnityTools.IsNullOrDestroyed(source)) throw new ArgumentNullException("source"); m_MouseInputSourcesList.Add(source); } /// /// Returns the number of possible pointers. /// Does not count the number actually assigned. /// public int GetMouseInputSourceCount(int playerId) { if(m_MouseInputSourcesList.Count == 0 && IsDefaultPlayer(playerId)) return 1; // fall back to default if nothing set int count = m_MouseInputSourcesList.Count; int pointerCount = 0; for(int i = 0; i < count; i++) { IMouseInputSource source = m_MouseInputSourcesList[i]; if(Utils.UnityTools.IsNullOrDestroyed(source)) continue; if(source.playerId != playerId) continue; pointerCount++; } return pointerCount; } /// /// Gets or sets the input source used for touch input. /// /// The Player Id that owns the touch input source. /// The index of the touch input source. If a Player owns more than one touch input source, set this to the index. See to determine the number of touch input sources that exist for a Player. WARNING: Currently only one touch input source is supported. public ITouchInputSource GetTouchInputSource(int playerId, int sourceIndex) { if(!Utils.UnityTools.IsNullOrDestroyed(m_UserDefaultTouchInputSource)) return m_UserDefaultTouchInputSource; return defaultTouchInputSource; } /// /// Removes the touch input source. /// /// The touch input source. public void RemoveTouchInputSource(ITouchInputSource source) { if(source == null) throw new ArgumentNullException("source"); // do not check if destroyed here so removal works on destroyed objects if(m_UserDefaultTouchInputSource == source) m_UserDefaultTouchInputSource = null; } /// /// Adds the touch input source. /// /// The touch input source. public void AddTouchInputSource(ITouchInputSource source) { if(Utils.UnityTools.IsNullOrDestroyed(source)) throw new ArgumentNullException("source"); m_UserDefaultTouchInputSource = source; } /// /// Returns the number of possible pointers. /// Does not count the number actually assigned. /// public int GetTouchInputSourceCount(int playerId) { return IsDefaultPlayer(playerId) ? 1 : 0; } protected void ClearMouseInputSources() { m_MouseInputSourcesList.Clear(); } protected virtual bool isMouseSupported { get { int count = m_MouseInputSourcesList.Count; if(count == 0) return defaultMouseInputSource.enabled; // fall back to default for(int i = 0; i < count; i++) { if(m_MouseInputSourcesList[i].enabled) return true; } return false; } } protected abstract bool IsDefaultPlayer(int playerId); protected bool GetPointerData(int playerId, int pointerIndex, int pointerTypeId, out PlayerPointerEventData data, bool create, PointerEventType pointerEventType) { // Get or create the by Player dictionary Dictionary[] pointerDataByIndex; if(!m_PlayerPointerData.TryGetValue(playerId, out pointerDataByIndex)) { pointerDataByIndex = new Dictionary[pointerIndex + 1]; for(int i = 0; i < pointerDataByIndex.Length; i++){ pointerDataByIndex[i] = new Dictionary(); } m_PlayerPointerData.Add(playerId, pointerDataByIndex); } // Expand array if necessary if(pointerIndex >= pointerDataByIndex.Length) { // array is not large enough, expand it Dictionary[] newArray = new Dictionary[pointerIndex + 1]; for(int i = 0; i < pointerDataByIndex.Length; i++) { newArray[i] = pointerDataByIndex[i]; } newArray[pointerIndex] = new Dictionary(); pointerDataByIndex = newArray; m_PlayerPointerData[playerId] = pointerDataByIndex; } // Get or create the pointer event data Dictionary byMouseIndexDict = pointerDataByIndex[pointerIndex]; if(!byMouseIndexDict.TryGetValue(pointerTypeId, out data)) { if (!create) return false; data = CreatePointerEventData(playerId, pointerIndex, pointerTypeId, pointerEventType); // create the event data byMouseIndexDict.Add(pointerTypeId, data); return true; } // Update the input sources data.mouseSource = pointerEventType == PointerEventType.Mouse ? GetMouseInputSource(playerId, pointerIndex) : null; data.touchSource = pointerEventType == PointerEventType.Touch ? GetTouchInputSource(playerId, pointerIndex) : null; return false; } private PlayerPointerEventData CreatePointerEventData(int playerId, int pointerIndex, int pointerTypeId, PointerEventType pointerEventType) { PlayerPointerEventData data = new PlayerPointerEventData(eventSystem) { playerId = playerId, inputSourceIndex = pointerIndex, pointerId = pointerTypeId, sourceType = pointerEventType }; if(pointerEventType == PointerEventType.Mouse) data.mouseSource = GetMouseInputSource(playerId, pointerIndex); // this can change in the future but it will be updated on Get else if(pointerEventType == PointerEventType.Touch) data.touchSource = GetTouchInputSource(playerId, pointerIndex); // this can change in the future but it will be updated on Get // Get the button index from the pointerTypeId if(pointerTypeId == kMouseLeftId) data.buttonIndex = 0; else if(pointerTypeId == kMouseRightId) data.buttonIndex = 1; else if(pointerTypeId == kMouseMiddleId) data.buttonIndex = 2; else { // encoded custom buttons if(pointerTypeId >= customButtonsStartingId && pointerTypeId <= customButtonsLastId) { data.buttonIndex = pointerTypeId - customButtonsStartingId; } } return data; } protected void RemovePointerData(PlayerPointerEventData data) { Dictionary[] pointerDataByIndex; if(m_PlayerPointerData.TryGetValue(data.playerId, out pointerDataByIndex)) { if((uint)data.inputSourceIndex < (uint)pointerDataByIndex.Length) { pointerDataByIndex[data.inputSourceIndex].Remove(data.pointerId); } } } protected PlayerPointerEventData GetTouchPointerEventData(int playerId, int touchDeviceIndex, Touch input, out bool pressed, out bool released) { PlayerPointerEventData pointerData; var created = GetPointerData(playerId, touchDeviceIndex, input.fingerId, out pointerData, true, PointerEventType.Touch); 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 = PlayerPointerEventData.InputButton.Left; eventSystem.RaycastAll(pointerData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); pointerData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); return pointerData; } protected class MouseState { private List m_TrackedButtons = new List(); public bool AnyPressesThisFrame() { for(int i = 0; i < m_TrackedButtons.Count; i++) { if(m_TrackedButtons[i].eventData.PressedThisFrame()) return true; } return false; } public bool AnyReleasesThisFrame() { for(int i = 0; i < m_TrackedButtons.Count; i++) { if(m_TrackedButtons[i].eventData.ReleasedThisFrame()) return true; } return false; } public ButtonState GetButtonState(int button) { ButtonState tracked = null; for(int i = 0; i < m_TrackedButtons.Count; 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(int button, PointerEventData.FramePressState stateForMouseButton, PlayerPointerEventData data) { var toModify = GetButtonState(button); toModify.eventData.buttonState = stateForMouseButton; toModify.eventData.buttonData = data; } } public class MouseButtonEventData { public PlayerPointerEventData.FramePressState buttonState; public PlayerPointerEventData buttonData; public bool PressedThisFrame() { return buttonState == PlayerPointerEventData.FramePressState.Pressed || buttonState == PlayerPointerEventData.FramePressState.PressedAndReleased; } public bool ReleasedThisFrame() { return buttonState == PlayerPointerEventData.FramePressState.Released || buttonState == PlayerPointerEventData.FramePressState.PressedAndReleased; } } private readonly MouseState m_MouseState = new MouseState(); protected virtual MouseState GetMousePointerEventData(int playerId, int mouseIndex) { IMouseInputSource mouseInputSource = GetMouseInputSource(playerId, mouseIndex); if(mouseInputSource == null) return null; // Populate the left button... PlayerPointerEventData leftData; var created = GetPointerData(playerId, mouseIndex, kMouseLeftId, out leftData, true, PointerEventType.Mouse); leftData.Reset(); if(created) leftData.position = mouseInputSource.screenPosition; Vector2 pos = mouseInputSource.screenPosition; if(mouseInputSource.locked || !mouseInputSource.enabled) { // 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 = mouseInputSource.wheelDelta; leftData.button = PlayerPointerEventData.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 PlayerPointerEventData rightData; GetPointerData(playerId, mouseIndex, kMouseRightId, out rightData, true, PointerEventType.Mouse); CopyFromTo(leftData, rightData); rightData.button = PlayerPointerEventData.InputButton.Right; PlayerPointerEventData middleData; GetPointerData(playerId, mouseIndex, kMouseMiddleId, out middleData, true, PointerEventType.Mouse); CopyFromTo(leftData, middleData); middleData.button = PlayerPointerEventData.InputButton.Middle; // Do remaining buttons for(int i = 3; i < mouseInputSource.buttonCount; i++) { PlayerPointerEventData data; GetPointerData(playerId, mouseIndex, customButtonsStartingId + i, out data, true, PointerEventType.Mouse); CopyFromTo(leftData, data); data.button = (PlayerPointerEventData.InputButton)(-1); } m_MouseState.SetButtonState(0, StateForMouseButton(playerId, mouseIndex, 0), leftData); m_MouseState.SetButtonState(1, StateForMouseButton(playerId, mouseIndex, 1), rightData); m_MouseState.SetButtonState(2, StateForMouseButton(playerId, mouseIndex, 2), middleData); // Do remaining buttons for(int i = 3; i < mouseInputSource.buttonCount; i++) { PlayerPointerEventData data; GetPointerData(playerId, mouseIndex, customButtonsStartingId + i, out data, false, PointerEventType.Mouse); m_MouseState.SetButtonState(i, StateForMouseButton(playerId, mouseIndex, i), data); } return m_MouseState; } protected PlayerPointerEventData GetLastPointerEventData(int playerId, int pointerIndex, int pointerTypeId, bool ignorePointerTypeId, PointerEventType pointerEventType) { PlayerPointerEventData data; if(!ignorePointerTypeId) { GetPointerData(playerId, pointerIndex, pointerTypeId, out data, false, pointerEventType); return data; } Dictionary[] pointerDataByIndex; if(!m_PlayerPointerData.TryGetValue(playerId, out pointerDataByIndex)) return null; if((uint)pointerIndex >= (uint)pointerDataByIndex.Length) return null; // Just get the first found regardless of the button id foreach(var kvp in pointerDataByIndex[pointerIndex]) return kvp.Value; return null; } private static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold) { if(!useDragThreshold) return true; return (pressPos - currentPos).sqrMagnitude >= threshold * threshold; } protected virtual void ProcessMove(PlayerPointerEventData pointerEvent) { GameObject targetGO; if(pointerEvent.sourceType == PointerEventType.Mouse) { IMouseInputSource source = GetMouseInputSource(pointerEvent.playerId, pointerEvent.inputSourceIndex); if(source != null) { targetGO = !source.enabled || source.locked ? null : pointerEvent.pointerCurrentRaycast.gameObject; } else { targetGO = null; } } else if(pointerEvent.sourceType == PointerEventType.Touch) { targetGO = pointerEvent.pointerCurrentRaycast.gameObject; } else throw new NotImplementedException(); HandlePointerExitAndEnter(pointerEvent, targetGO); } protected virtual void ProcessDrag(PlayerPointerEventData pointerEvent) { if(!pointerEvent.IsPointerMoving() || pointerEvent.pointerDrag == null) return; if(pointerEvent.sourceType == PointerEventType.Mouse) { IMouseInputSource source = GetMouseInputSource(pointerEvent.playerId, pointerEvent.inputSourceIndex); if(source == null || source.locked || !source.enabled) 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 pointerTypeId) { foreach(var perPlayer in m_PlayerPointerData) { foreach(var perIndex in perPlayer.Value) { PlayerPointerEventData data; if(!perIndex.TryGetValue(pointerTypeId, out data)) continue; if(data.pointerEnter != null) return true; } } return false; } protected void ClearSelection() { var baseEventData = GetBaseEventData(); foreach(var playerSetKVP in m_PlayerPointerData) { var byIndex = playerSetKVP.Value; for(int i = 0; i < byIndex.Length; i++) { foreach(var buttonSetKVP in byIndex[i]) { // clear all selection HandlePointerExitAndEnter(buttonSetKVP.Value, null); } byIndex[i].Clear(); } } eventSystem.SetSelectedGameObject(null, baseEventData); } public override string ToString() { var sb = new StringBuilder("Pointer Input Module of type: " + GetType()); sb.AppendLine(); foreach(var playerSetKVP in m_PlayerPointerData) { sb.AppendLine("Player Id: " + playerSetKVP.Key); var byIndex = playerSetKVP.Value; for(int i = 0; i < byIndex.Length; i++) { sb.AppendLine("Pointer Index: " + i); foreach(var buttonSetKVP in byIndex[i]) { sb.AppendLine("Button Id: " + buttonSetKVP.Key); sb.AppendLine(buttonSetKVP.Value.ToString()); } } } return sb.ToString(); } 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); } } 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; } protected PointerEventData.FramePressState StateForMouseButton(int playerId, int mouseIndex, int buttonId) { IMouseInputSource mouseInputSource = GetMouseInputSource(playerId, mouseIndex); if(mouseInputSource == null) return PointerEventData.FramePressState.NotChanged; var pressed = mouseInputSource.GetButtonDown(buttonId); var released = mouseInputSource.GetButtonUp(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 int m_Button = 0; public MouseButtonEventData eventData { get { return m_EventData; } set { m_EventData = value; } } public int button { get { return m_Button; } set { m_Button = value; } } private MouseButtonEventData m_EventData; } private sealed class UnityInputSource : IMouseInputSource, ITouchInputSource { private Vector2 m_MousePosition; private Vector2 m_MousePositionPrev; private int m_LastUpdatedFrame = -1; int IMouseInputSource.playerId { get { TryUpdate(); return 0; } } int ITouchInputSource.playerId { get { TryUpdate(); return 0; } } bool IMouseInputSource.enabled { get { TryUpdate(); return Input.mousePresent; } } bool IMouseInputSource.locked { get { TryUpdate(); #if UNITY_5_PLUS return Cursor.lockState == CursorLockMode.Locked; #else return false; #endif } } int IMouseInputSource.buttonCount { get { TryUpdate(); return 3; } } bool IMouseInputSource.GetButtonDown(int button) { TryUpdate(); return Input.GetMouseButtonDown(button); } bool IMouseInputSource.GetButtonUp(int button) { TryUpdate(); return Input.GetMouseButtonUp(button); } bool IMouseInputSource.GetButton(int button) { TryUpdate(); return Input.GetMouseButton(button); } Vector2 IMouseInputSource.screenPosition { get { TryUpdate(); return Input.mousePosition; } } Vector2 IMouseInputSource.screenPositionDelta { get { TryUpdate(); return m_MousePosition - m_MousePositionPrev; } } Vector2 IMouseInputSource.wheelDelta { get { TryUpdate(); return Input.mouseScrollDelta; } } bool ITouchInputSource.touchSupported { get { TryUpdate(); return Input.touchSupported; } } int ITouchInputSource.touchCount { get { TryUpdate(); return Input.touchCount; } } Touch ITouchInputSource.GetTouch(int index) { TryUpdate(); return Input.GetTouch(index); } private void TryUpdate() { if(Time.frameCount == m_LastUpdatedFrame) return; m_LastUpdatedFrame = Time.frameCount; m_MousePositionPrev = m_MousePosition; m_MousePosition = Input.mousePosition; } } } public enum PointerEventType { Mouse = 0, Touch = 1 } }