/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; namespace UnityEngine.EventSystems { /// /// VR extension of PointerInputModule which supports gaze and controller pointing. /// public class OVRInputModule : PointerInputModule { [Tooltip("Object which points with Z axis. E.g. CentreEyeAnchor from OVRCameraRig")] public Transform rayTransform; public OVRCursor m_Cursor; [Tooltip("Gamepad button to act as gaze click")] public OVRInput.Button joyPadClickButton = OVRInput.Button.One; [Tooltip("Keyboard button to act as gaze click")] public KeyCode gazeClickKey = KeyCode.Space; [Header("Physics")] [Tooltip("Perform an sphere cast to determine correct depth for gaze pointer")] public bool performSphereCastForGazepointer; [Header("Gamepad Stick Scroll")] [Tooltip("Enable scrolling with the right stick on a gamepad")] public bool useRightStickScroll = true; [Tooltip("Deadzone for right stick to prevent accidental scrolling")] public float rightStickDeadZone = 0.15f; [Header("Touchpad Swipe Scroll")] [Tooltip("Enable scrolling by swiping the touchpad")] public bool useSwipeScroll = true; [Tooltip("Minimum trackpad movement in pixels to start swiping")] public float swipeDragThreshold = 2; [Tooltip("Distance scrolled when swipe scroll occurs")] public float swipeDragScale = 1f; [Tooltip("Invert X axis on touchpad")] public bool InvertSwipeXAxis = false; // The raycaster that gets to do pointer interaction (e.g. with a mouse), gaze interaction always works [NonSerialized] public OVRRaycaster activeGraphicRaycaster; [Header("Dragging")] [Tooltip("Minimum pointer movement in degrees to start dragging")] public float angleDragThreshold = 1; [SerializeField] private float m_SpherecastRadius = 1.0f; // The following region contains code exactly the same as the implementation // of StandaloneInputModule. It is copied here rather than inheriting from StandaloneInputModule // because most of StandaloneInputModule is private so it isn't possible to easily derive from. // Future changes from Unity to StandaloneInputModule will make it possible for this class to // derive from StandaloneInputModule instead of PointerInput module. // // The following functions are not present in the following region since they have modified // versions in the next region: // Process // ProcessMouseEvent // UseMouse #region StandaloneInputModule code private float m_NextAction; private Vector2 m_LastMousePosition; private Vector2 m_MousePosition; protected OVRInputModule() {} #if UNITY_EDITOR protected override void Reset() { allowActivationOnMobileDevice = true; } #endif [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)] public enum InputMode { Mouse, Buttons } [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)] public InputMode inputMode { get { return InputMode.Mouse; } } [Header("Standalone Input Module")] [SerializeField] private string m_HorizontalAxis = "Horizontal"; /// /// Name of the vertical axis for movement (if axis events are used). /// [SerializeField] private string m_VerticalAxis = "Vertical"; /// /// Name of the submit button. /// [SerializeField] private string m_SubmitButton = "Submit"; /// /// Name of the submit button. /// [SerializeField] private string m_CancelButton = "Cancel"; [SerializeField] private float m_InputActionsPerSecond = 10; [SerializeField] private bool m_AllowActivationOnMobileDevice; public bool allowActivationOnMobileDevice { get { return m_AllowActivationOnMobileDevice; } set { m_AllowActivationOnMobileDevice = value; } } public float inputActionsPerSecond { get { return m_InputActionsPerSecond; } set { m_InputActionsPerSecond = value; } } /// /// Name of the horizontal axis for movement (if axis events are used). /// public string horizontalAxis { get { return m_HorizontalAxis; } set { m_HorizontalAxis = value; } } /// /// Name of the vertical axis for movement (if axis events are used). /// public string verticalAxis { get { return m_VerticalAxis; } set { m_VerticalAxis = value; } } public string submitButton { get { return m_SubmitButton; } set { m_SubmitButton = value; } } public string cancelButton { get { return m_CancelButton; } set { m_CancelButton = value; } } public override void UpdateModule() { #if ENABLE_LEGACY_INPUT_MANAGER m_LastMousePosition = m_MousePosition; m_MousePosition = Input.mousePosition; #endif } public override bool IsModuleSupported() { // Check for mouse presence instead of whether touch is supported, // as you can connect mouse to a tablet and in that case we'd want // to use StandaloneInputModule for non-touch input events. return m_AllowActivationOnMobileDevice || Input.mousePresent; } public override bool ShouldActivateModule() { if (!base.ShouldActivateModule()) return false; #if ENABLE_LEGACY_INPUT_MANAGER var shouldActivate = Input.GetButtonDown(m_SubmitButton); shouldActivate |= Input.GetButtonDown(m_CancelButton); shouldActivate |= !Mathf.Approximately(Input.GetAxisRaw(m_HorizontalAxis), 0.0f); shouldActivate |= !Mathf.Approximately(Input.GetAxisRaw(m_VerticalAxis), 0.0f); shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f; shouldActivate |= Input.GetMouseButtonDown(0); return shouldActivate; #else return false; #endif } public override void ActivateModule() { base.ActivateModule(); #if ENABLE_LEGACY_INPUT_MANAGER m_MousePosition = Input.mousePosition; m_LastMousePosition = Input.mousePosition; #endif var toSelect = eventSystem.currentSelectedGameObject; if (toSelect == null) toSelect = eventSystem.firstSelectedGameObject; eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData()); } public override void DeactivateModule() { base.DeactivateModule(); ClearSelection(); } /// /// Process submit keys. /// private bool SendSubmitEventToSelectedObject() { if (eventSystem.currentSelectedGameObject == null) return false; var data = GetBaseEventData(); #if ENABLE_LEGACY_INPUT_MANAGER if (Input.GetButtonDown(m_SubmitButton)) ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler); if (Input.GetButtonDown(m_CancelButton)) ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler); #endif return data.used; } private bool AllowMoveEventProcessing(float time) { #if ENABLE_LEGACY_INPUT_MANAGER bool allow = Input.GetButtonDown(m_HorizontalAxis); allow |= Input.GetButtonDown(m_VerticalAxis); allow |= (time > m_NextAction); return allow; #else return false; #endif } private Vector2 GetRawMoveVector() { Vector2 move = Vector2.zero; move.x = Input.GetAxisRaw(m_HorizontalAxis); move.y = Input.GetAxisRaw(m_VerticalAxis); if (Input.GetButtonDown(m_HorizontalAxis)) { if (move.x < 0) move.x = -1f; if (move.x > 0) move.x = 1f; } if (Input.GetButtonDown(m_VerticalAxis)) { if (move.y < 0) move.y = -1f; if (move.y > 0) move.y = 1f; } return move; } /// /// Process keyboard events. /// private bool SendMoveEventToSelectedObject() { float time = Time.unscaledTime; if (!AllowMoveEventProcessing(time)) return false; Vector2 movement = GetRawMoveVector(); // Debug.Log(m_ProcessingEvent.rawType + " axis:" + m_AllowAxisEvents + " value:" + "(" + x + "," + y + ")"); var axisEventData = GetAxisEventData(movement.x, movement.y, 0.6f); if (!Mathf.Approximately(axisEventData.moveVector.x, 0f) || !Mathf.Approximately(axisEventData.moveVector.y, 0f)) { ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, axisEventData, ExecuteEvents.moveHandler); } m_NextAction = time + 1f / m_InputActionsPerSecond; return axisEventData.used; } private bool SendUpdateEventToSelectedObject() { if (eventSystem.currentSelectedGameObject == null) return false; var data = GetBaseEventData(); ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler); return data.used; } /// /// Process the current mouse press. /// private void ProcessMousePress(MouseButtonEventData data) { var pointerEvent = data.buttonData; var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject; // PointerDown notification if (data.PressedThisFrame()) { pointerEvent.eligibleForClick = true; pointerEvent.delta = Vector2.zero; pointerEvent.dragging = false; pointerEvent.useDragThreshold = true; pointerEvent.pressPosition = pointerEvent.position; if (pointerEvent.IsVRPointer()) { #if ENABLE_LEGACY_INPUT_MANAGER pointerEvent.SetSwipeStart(Input.mousePosition); #endif } pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast; DeselectIfSelectionChanged(currentOverGo, pointerEvent); // search for the control that will receive the press // if we can't find a press handler set the press // handler to be what would receive a click. var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler); // didnt find a press handler... search for a click handler if (newPressed == null) newPressed = ExecuteEvents.GetEventHandler(currentOverGo); // Debug.Log("Pressed: " + newPressed); float time = Time.unscaledTime; if (newPressed == pointerEvent.lastPress) { var diffTime = time - pointerEvent.clickTime; if (diffTime < 0.3f) ++pointerEvent.clickCount; else pointerEvent.clickCount = 1; pointerEvent.clickTime = time; } else { pointerEvent.clickCount = 1; } pointerEvent.pointerPress = newPressed; pointerEvent.rawPointerPress = currentOverGo; pointerEvent.clickTime = time; // Save the drag handler as well pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler(currentOverGo); if (pointerEvent.pointerDrag != null) ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag); } // PointerUp notification if (data.ReleasedThisFrame()) { // Debug.Log("Executing pressup on: " + pointer.pointerPress); ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); // Debug.Log("KeyCode: " + pointer.eventData.keyCode); // see if we mouse up on the same element that we clicked on... var pointerUpHandler = ExecuteEvents.GetEventHandler(currentOverGo); // PointerClick and Drop events if (pointerEvent.pointerPress == pointerUpHandler && pointerEvent.eligibleForClick) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerClickHandler); } else if (pointerEvent.pointerDrag != null) { ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler); } pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; if (pointerEvent.pointerDrag != null && pointerEvent.dragging) ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler); pointerEvent.dragging = false; pointerEvent.pointerDrag = null; // redo pointer enter / exit to refresh state // so that if we moused over somethign that ignored it before // due to having pressed on something else // it now gets it. if (currentOverGo != pointerEvent.pointerEnter) { HandlePointerExitAndEnter(pointerEvent, null); HandlePointerExitAndEnter(pointerEvent, currentOverGo); } } } #endregion #region Modified StandaloneInputModule methods /// /// Process all mouse events. This is the same as the StandaloneInputModule version except that /// it takes MouseState as a parameter, allowing it to be used for both Gaze and Mouse /// pointerss. /// private void ProcessMouseEvent(MouseState mouseData) { var pressed = mouseData.AnyPressesThisFrame(); var released = mouseData.AnyReleasesThisFrame(); var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData; if (!UseMouse(pressed, released, leftButtonData.buttonData)) return; // Process the first mouse button fully ProcessMousePress(leftButtonData); ProcessMove(leftButtonData.buttonData); ProcessDrag(leftButtonData.buttonData); // Now process right / middle clicks ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData); ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData); ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData); ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData); if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f)) { var scrollHandler = ExecuteEvents.GetEventHandler(leftButtonData.buttonData.pointerCurrentRaycast.gameObject); ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler); } } /// /// Process this InputModule. Same as the StandaloneInputModule version, except that it calls /// ProcessMouseEvent twice, once for gaze pointers, and once for mouse pointers. /// public override void Process() { bool usedEvent = SendUpdateEventToSelectedObject(); if (eventSystem.sendNavigationEvents) { if (!usedEvent) usedEvent |= SendMoveEventToSelectedObject(); if (!usedEvent) SendSubmitEventToSelectedObject(); } ProcessMouseEvent(GetGazePointerData()); #if !UNITY_ANDROID ProcessMouseEvent(GetCanvasPointerData()); #endif } /// /// Decide if mouse events need to be processed this frame. Same as StandloneInputModule except /// that the IsPointerMoving method from this class is used, instead of the method on PointerEventData /// private static bool UseMouse(bool pressed, bool released, PointerEventData pointerData) { if (pressed || released || IsPointerMoving(pointerData) || pointerData.IsScrolling()) return true; return false; } #endregion /// /// Convenience function for cloning PointerEventData /// /// Copy this value /// to this object protected void CopyFromTo(OVRPointerEventData @from, OVRPointerEventData @to) { @to.position = @from.position; @to.delta = @from.delta; @to.scrollDelta = @from.scrollDelta; @to.pointerCurrentRaycast = @from.pointerCurrentRaycast; @to.pointerEnter = @from.pointerEnter; @to.worldSpaceRay = @from.worldSpaceRay; } /// /// Convenience function for cloning PointerEventData /// /// Copy this value /// to this object protected new 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; } // In the following region we extend the PointerEventData system implemented in PointerInputModule // We define an additional dictionary for ray(e.g. gaze) based pointers. Mouse pointers still use the dictionary // in PointerInputModule #region PointerEventData pool // Pool for OVRRayPointerEventData for ray based pointers protected Dictionary m_VRRayPointerData = new Dictionary(); protected bool GetPointerData(int id, out OVRPointerEventData data, bool create) { if (!m_VRRayPointerData.TryGetValue(id, out data) && create) { data = new OVRPointerEventData(eventSystem) { pointerId = id, }; m_VRRayPointerData.Add(id, data); return true; } return false; } /// /// Clear pointer state for both types of pointer /// protected new void ClearSelection() { var baseEventData = GetBaseEventData(); foreach (var pointer in m_PointerData.Values) { // clear all selection HandlePointerExitAndEnter(pointer, null); } foreach (var pointer in m_VRRayPointerData.Values) { // clear all selection HandlePointerExitAndEnter(pointer, null); } m_PointerData.Clear(); eventSystem.SetSelectedGameObject(null, baseEventData); } #endregion /// /// For RectTransform, calculate it's normal in world space /// static Vector3 GetRectTransformNormal(RectTransform rectTransform) { Vector3[] corners = new Vector3[4]; rectTransform.GetWorldCorners(corners); Vector3 BottomEdge = corners[3] - corners[0]; Vector3 LeftEdge = corners[1] - corners[0]; rectTransform.GetWorldCorners(corners); return Vector3.Cross(BottomEdge, LeftEdge).normalized; } private readonly MouseState m_MouseState = new MouseState(); // The following 2 functions are equivalent to PointerInputModule.GetMousePointerEventData but are customized to // get data for ray pointers and canvas mouse pointers. /// /// State for a pointer controlled by a world space ray. E.g. gaze pointer /// /// virtual protected MouseState GetGazePointerData() { // Get the OVRRayPointerEventData reference OVRPointerEventData leftData; GetPointerData(kMouseLeftId, out leftData, true ); leftData.Reset(); //Now set the world space ray. This ray is what the user uses to point at UI elements leftData.worldSpaceRay = new Ray(rayTransform.position, rayTransform.forward); leftData.scrollDelta = GetExtraScrollDelta(); //Populate some default values leftData.button = PointerEventData.InputButton.Left; leftData.useDragThreshold = true; // Perform raycast to find intersections with world eventSystem.RaycastAll(leftData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); leftData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); m_Cursor.SetCursorRay(rayTransform); OVRRaycaster ovrRaycaster = raycast.module as OVRRaycaster; // We're only interested in intersections from OVRRaycasters if (ovrRaycaster) { // The Unity UI system expects event data to have a screen position // so even though this raycast came from a world space ray we must get a screen // space position for the camera attached to this raycaster for compatability leftData.position = ovrRaycaster.GetScreenPosition(raycast); // Find the world position and normal the Graphic the ray intersected RectTransform graphicRect = raycast.gameObject.GetComponent(); if (graphicRect != null) { // Set are gaze indicator with this world position and normal Vector3 worldPos = raycast.worldPosition; Vector3 normal = GetRectTransformNormal(graphicRect); m_Cursor.SetCursorStartDest(rayTransform.position, worldPos, normal); } } // Now process physical raycast intersections OVRPhysicsRaycaster physicsRaycaster = raycast.module as OVRPhysicsRaycaster; if (physicsRaycaster) { Vector3 position = raycast.worldPosition; if (performSphereCastForGazepointer) { // Here we cast a sphere into the scene rather than a ray. This gives a more accurate depth // for positioning a circular gaze pointer List results = new List(); physicsRaycaster.Spherecast(leftData, results, m_SpherecastRadius); if (results.Count > 0 && results[0].distance < raycast.distance) { position = results[0].worldPosition; } } leftData.position = physicsRaycaster.GetScreenPos(raycast.worldPosition); m_Cursor.SetCursorStartDest(rayTransform.position, position, raycast.worldNormal); } // Stick default data values in right and middle slots for compatability // copy the apropriate data into right and middle slots OVRPointerEventData rightData; GetPointerData(kMouseRightId, out rightData, true ); CopyFromTo(leftData, rightData); rightData.button = PointerEventData.InputButton.Right; OVRPointerEventData middleData; GetPointerData(kMouseMiddleId, out middleData, true ); CopyFromTo(leftData, middleData); middleData.button = PointerEventData.InputButton.Middle; m_MouseState.SetButtonState(PointerEventData.InputButton.Left, GetGazeButtonState(), leftData); m_MouseState.SetButtonState(PointerEventData.InputButton.Right, PointerEventData.FramePressState.NotChanged, rightData); m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, PointerEventData.FramePressState.NotChanged, middleData); return m_MouseState; } /// /// Get state for pointer which is a pointer moving in world space across the surface of a world space canvas. /// /// protected MouseState GetCanvasPointerData() { // Get the OVRRayPointerEventData reference PointerEventData leftData; GetPointerData(kMouseLeftId, out leftData, true ); leftData.Reset(); // Setup default values here. Set position to zero because we don't actually know the pointer // positions. Each canvas knows the position of its canvas pointer. leftData.position = Vector2.zero; #if ENABLE_LEGACY_INPUT_MANAGER leftData.scrollDelta = Input.mouseScrollDelta; #endif leftData.button = PointerEventData.InputButton.Left; if (activeGraphicRaycaster) { // Let the active raycaster find intersections on its canvas activeGraphicRaycaster.RaycastPointer(leftData, m_RaycastResultCache); var raycast = FindFirstRaycast(m_RaycastResultCache); leftData.pointerCurrentRaycast = raycast; m_RaycastResultCache.Clear(); OVRRaycaster ovrRaycaster = raycast.module as OVRRaycaster; if (ovrRaycaster) // raycast may not actually contain a result { // The Unity UI system expects event data to have a screen position // so even though this raycast came from a world space ray we must get a screen // space position for the camera attached to this raycaster for compatability Vector2 position = ovrRaycaster.GetScreenPosition(raycast); leftData.delta = position - leftData.position; leftData.position = position; } } // copy the apropriate data into right and middle slots PointerEventData rightData; GetPointerData(kMouseRightId, out rightData, true ); CopyFromTo(leftData, rightData); rightData.button = PointerEventData.InputButton.Right; PointerEventData middleData; GetPointerData(kMouseMiddleId, out middleData, true ); CopyFromTo(leftData, middleData); middleData.button = PointerEventData.InputButton.Middle; #if ENABLE_LEGACY_INPUT_MANAGER 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); #endif return m_MouseState; } /// /// New version of ShouldStartDrag implemented first in PointerInputModule. This version differs in that /// for ray based pointers it makes a decision about whether a drag should start based on the angular change /// the pointer has made so far, as seen from the camera. This also works when the world space ray is /// translated rather than rotated, since the beginning and end of the movement are considered as angle from /// the same point. /// private bool ShouldStartDrag(PointerEventData pointerEvent) { if (!pointerEvent.useDragThreshold) return true; if (!pointerEvent.IsVRPointer()) { // Same as original behaviour for canvas based pointers return (pointerEvent.pressPosition - pointerEvent.position).sqrMagnitude >= eventSystem.pixelDragThreshold * eventSystem.pixelDragThreshold; } else { #if UNITY_ANDROID && !UNITY_EDITOR // On android allow swiping to start drag if (useSwipeScroll && ((Vector3)pointerEvent.GetSwipeStart() - Input.mousePosition).magnitude > swipeDragThreshold) { return true; } #endif // When it's not a screen space pointer we have to look at the angle it moved rather than the pixels distance // For gaze based pointing screen-space distance moved will always be near 0 Vector3 cameraPos = pointerEvent.pressEventCamera.transform.position; Vector3 pressDir = (pointerEvent.pointerPressRaycast.worldPosition - cameraPos).normalized; Vector3 currentDir = (pointerEvent.pointerCurrentRaycast.worldPosition - cameraPos).normalized; return Vector3.Dot(pressDir, currentDir) < Mathf.Cos(Mathf.Deg2Rad * (angleDragThreshold)); } } /// /// The purpose of this function is to allow us to switch between using the standard IsPointerMoving /// method for mouse driven pointers, but to always return true when it's a ray based pointer. /// All real-world ray-based input devices are always moving so for simplicity we just return true /// for them. /// /// If PointerEventData.IsPointerMoving was virtual we could just override that in /// OVRRayPointerEventData. /// /// /// static bool IsPointerMoving(PointerEventData pointerEvent) { if (pointerEvent.IsVRPointer()) return true; else return pointerEvent.IsPointerMoving(); } protected Vector2 SwipeAdjustedPosition(Vector2 originalPosition, PointerEventData pointerEvent) { #if UNITY_ANDROID && !UNITY_EDITOR // On android we use the touchpad position (accessed through Input.mousePosition) to modify // the effective cursor position for events related to dragging. This allows the user to // use the touchpad to drag draggable UI elements if (useSwipeScroll) { Vector2 delta = (Vector2)Input.mousePosition - pointerEvent.GetSwipeStart(); if (InvertSwipeXAxis) delta.x *= -1; return originalPosition + delta * swipeDragScale; } #endif return originalPosition; } /// /// Exactly the same as the code from PointerInputModule, except that we call our own /// IsPointerMoving. /// /// This would also not be necessary if PointerEventData.IsPointerMoving was virtual /// /// protected override void ProcessDrag(PointerEventData pointerEvent) { Vector2 originalPosition = pointerEvent.position; bool moving = IsPointerMoving(pointerEvent); if (moving && pointerEvent.pointerDrag != null && !pointerEvent.dragging && ShouldStartDrag(pointerEvent)) { if (pointerEvent.IsVRPointer()) { //adjust the position used based on swiping action. Allowing the user to //drag items by swiping on the touchpad pointerEvent.position = SwipeAdjustedPosition (originalPosition, pointerEvent); } ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.beginDragHandler); pointerEvent.dragging = true; } // Drag notification if (pointerEvent.dragging && moving && pointerEvent.pointerDrag != null) { if (pointerEvent.IsVRPointer()) { pointerEvent.position = SwipeAdjustedPosition(originalPosition, pointerEvent); } // 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); } } /// /// Get state of button corresponding to gaze pointer /// /// virtual protected PointerEventData.FramePressState GetGazeButtonState() { //todo: enable for Unity Input System #if ENABLE_LEGACY_INPUT_MANAGER var pressed = Input.GetKeyDown(gazeClickKey) || OVRInput.GetDown(joyPadClickButton); var released = Input.GetKeyUp(gazeClickKey) || OVRInput.GetUp(joyPadClickButton); #if UNITY_ANDROID && !UNITY_EDITOR pressed |= Input.GetMouseButtonDown(0); released |= Input.GetMouseButtonUp(0); #endif #else var pressed = OVRInput.GetDown(joyPadClickButton); var released = OVRInput.GetUp(joyPadClickButton); #endif if (pressed && released) return PointerEventData.FramePressState.PressedAndReleased; if (pressed) return PointerEventData.FramePressState.Pressed; if (released) return PointerEventData.FramePressState.Released; return PointerEventData.FramePressState.NotChanged; } /// /// Get extra scroll delta from gamepad /// protected Vector2 GetExtraScrollDelta() { Vector2 scrollDelta = new Vector2(); if (useRightStickScroll) { Vector2 s = OVRInput.Get(OVRInput.Axis2D.SecondaryThumbstick); if (Mathf.Abs(s.x) < rightStickDeadZone) s.x = 0; if (Mathf.Abs(s.y) < rightStickDeadZone) s.y = 0; scrollDelta = s; } return scrollDelta; } }; }