459 lines
17 KiB
C#
459 lines
17 KiB
C#
// Copyright 2017 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the MIT License, you may not use this file except in
|
|
// compliance with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.opensource.org/licenses/mit-license.php
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// 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 UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
#if UNITY_2017_2_OR_NEWER
|
|
using UnityEngine.XR;
|
|
#else
|
|
using XRSettings = UnityEngine.VR.VRSettings;
|
|
#endif // UNITY_2017_2_OR_NEWER
|
|
|
|
/// Implementation of _GvrPointerInputModule_
|
|
public class GvrPointerInputModuleImpl {
|
|
/// Interface for controlling the actual InputModule.
|
|
public IGvrInputModuleController ModuleController { get; set; }
|
|
|
|
/// Interface for executing events.
|
|
public IGvrEventExecutor EventExecutor { get; set; }
|
|
|
|
/// Determines whether pointer input is active in VR Mode only (`true`), or all of the
|
|
/// time (`false`). Set to false if you plan to use direct screen taps or other
|
|
/// input when not in VR Mode.
|
|
public bool VrModeOnly { get; set; }
|
|
|
|
/// The GvrPointerScrollInput used to route Scroll Events through _EventSystem_
|
|
public GvrPointerScrollInput ScrollInput { get; set; }
|
|
|
|
/// PointerEventData from the most recent frame.
|
|
public PointerEventData CurrentEventData { get; private set; }
|
|
|
|
/// The GvrBasePointer which will be responding to pointer events.
|
|
public GvrBasePointer Pointer {
|
|
get {
|
|
return pointer;
|
|
}
|
|
set {
|
|
if (pointer == value) {
|
|
return;
|
|
}
|
|
|
|
TryExitPointer();
|
|
|
|
pointer = value;
|
|
}
|
|
}
|
|
|
|
private GvrBasePointer pointer;
|
|
private Vector2 lastPose;
|
|
private bool isPointerHovering = false;
|
|
|
|
// Active state
|
|
private bool isActive = false;
|
|
|
|
public bool ShouldActivateModule() {
|
|
bool isVrModeEnabled = !VrModeOnly;
|
|
isVrModeEnabled |= XRSettings.enabled;
|
|
|
|
bool activeState = ModuleController.ShouldActivate() && isVrModeEnabled;
|
|
|
|
if (activeState != isActive) {
|
|
isActive = activeState;
|
|
}
|
|
|
|
return activeState;
|
|
}
|
|
|
|
public void DeactivateModule() {
|
|
TryExitPointer();
|
|
ModuleController.Deactivate();
|
|
if (CurrentEventData != null) {
|
|
HandlePendingClick();
|
|
HandlePointerExitAndEnter(CurrentEventData, null);
|
|
CurrentEventData = null;
|
|
}
|
|
ModuleController.eventSystem.SetSelectedGameObject(null, ModuleController.GetBaseEventData());
|
|
}
|
|
|
|
public bool IsPointerOverGameObject(int pointerId) {
|
|
return CurrentEventData != null && CurrentEventData.pointerEnter != null;
|
|
}
|
|
|
|
public void Process() {
|
|
// If the pointer is inactive, make sure it is exited if necessary.
|
|
if (!IsPointerActiveAndAvailable()) {
|
|
TryExitPointer();
|
|
}
|
|
|
|
// Save the previous Game Object
|
|
GameObject previousObject = GetCurrentGameObject();
|
|
|
|
CastRay();
|
|
UpdateCurrentObject(previousObject);
|
|
UpdatePointer(previousObject);
|
|
|
|
// True during the frame that the trigger has been pressed.
|
|
bool triggerDown = false;
|
|
// True if the trigger is held down.
|
|
bool triggering = false;
|
|
|
|
if (IsPointerActiveAndAvailable()) {
|
|
triggerDown = Pointer.TriggerDown;
|
|
triggering = Pointer.Triggering;
|
|
}
|
|
|
|
bool handlePendingClickRequired = !triggering;
|
|
|
|
// Handle input
|
|
if (!triggerDown && triggering) {
|
|
HandleDrag();
|
|
} else if (triggerDown && !CurrentEventData.eligibleForClick) {
|
|
// New trigger action.
|
|
HandleTriggerDown();
|
|
} else if (handlePendingClickRequired) {
|
|
// Check if there is a pending click to handle.
|
|
HandlePendingClick();
|
|
}
|
|
|
|
ScrollInput.HandleScroll(GetCurrentGameObject(), CurrentEventData, Pointer, EventExecutor);
|
|
}
|
|
|
|
private void CastRay() {
|
|
Vector2 currentPose = lastPose;
|
|
if (IsPointerActiveAndAvailable()) {
|
|
currentPose = GvrMathHelpers.NormalizedCartesianToSpherical(Pointer.PointerTransform.forward);
|
|
}
|
|
|
|
if (CurrentEventData == null) {
|
|
CurrentEventData = new PointerEventData(ModuleController.eventSystem);
|
|
lastPose = currentPose;
|
|
}
|
|
|
|
// Store the previous raycast result.
|
|
RaycastResult previousRaycastResult = CurrentEventData.pointerCurrentRaycast;
|
|
|
|
// The initial cast must use the enter radius.
|
|
if (IsPointerActiveAndAvailable()) {
|
|
Pointer.ShouldUseExitRadiusForRaycast = false;
|
|
}
|
|
|
|
// Cast a ray into the scene
|
|
CurrentEventData.Reset();
|
|
// Set the position to the center of the camera.
|
|
// This is only necessary if using the built-in Unity raycasters.
|
|
RaycastResult raycastResult;
|
|
CurrentEventData.position = GvrVRHelpers.GetViewportCenter();
|
|
bool isPointerActiveAndAvailable = IsPointerActiveAndAvailable();
|
|
if (isPointerActiveAndAvailable) {
|
|
RaycastAll();
|
|
raycastResult = ModuleController.FindFirstRaycast(ModuleController.RaycastResultCache);
|
|
if (Pointer.ControllerInputDevice == null || Pointer.ControllerInputDevice.IsDominantHand) {
|
|
CurrentEventData.pointerId = (int)GvrControllerHand.Dominant;
|
|
} else {
|
|
CurrentEventData.pointerId = (int)GvrControllerHand.NonDominant;
|
|
}
|
|
} else {
|
|
raycastResult = new RaycastResult();
|
|
raycastResult.Clear();
|
|
}
|
|
|
|
// If we were already pointing at an object we must check that object against the exit radius
|
|
// to make sure we are no longer pointing at it to prevent flicker.
|
|
if (previousRaycastResult.gameObject != null
|
|
&& raycastResult.gameObject != previousRaycastResult.gameObject
|
|
&& isPointerActiveAndAvailable) {
|
|
Pointer.ShouldUseExitRadiusForRaycast = true;
|
|
RaycastAll();
|
|
RaycastResult firstResult = ModuleController.FindFirstRaycast(ModuleController.RaycastResultCache);
|
|
if (firstResult.gameObject == previousRaycastResult.gameObject) {
|
|
raycastResult = firstResult;
|
|
}
|
|
}
|
|
|
|
if (raycastResult.gameObject != null && raycastResult.worldPosition == Vector3.zero) {
|
|
raycastResult.worldPosition =
|
|
GvrMathHelpers.GetIntersectionPosition(CurrentEventData.enterEventCamera, raycastResult);
|
|
}
|
|
|
|
CurrentEventData.pointerCurrentRaycast = raycastResult;
|
|
|
|
// Find the real screen position associated with the raycast
|
|
// Based on the results of the hit and the state of the pointerData.
|
|
if (raycastResult.gameObject != null) {
|
|
CurrentEventData.position = raycastResult.screenPosition;
|
|
} else if (IsPointerActiveAndAvailable() && CurrentEventData.enterEventCamera != null) {
|
|
Vector3 pointerPos = Pointer.MaxPointerEndPoint;
|
|
CurrentEventData.position = CurrentEventData.enterEventCamera.WorldToScreenPoint(pointerPos);
|
|
}
|
|
|
|
ModuleController.RaycastResultCache.Clear();
|
|
CurrentEventData.delta = currentPose - lastPose;
|
|
lastPose = currentPose;
|
|
|
|
// Check to make sure the Raycaster being used is a GvrRaycaster.
|
|
if (raycastResult.module != null
|
|
&& !(raycastResult.module is GvrPointerGraphicRaycaster)
|
|
&& !(raycastResult.module is GvrPointerPhysicsRaycaster)) {
|
|
Debug.LogWarning("Using Raycaster (Raycaster: " + raycastResult.module.GetType() +
|
|
", Object: " + raycastResult.module.name + "). It is recommended to use " +
|
|
"GvrPointerPhysicsRaycaster or GvrPointerGrahpicRaycaster with GvrPointerInputModule.");
|
|
}
|
|
}
|
|
|
|
private void UpdateCurrentObject(GameObject previousObject) {
|
|
if (CurrentEventData == null) {
|
|
return;
|
|
}
|
|
// Send enter events and update the highlight.
|
|
GameObject currentObject = GetCurrentGameObject(); // Get the pointer target
|
|
HandlePointerExitAndEnter(CurrentEventData, currentObject);
|
|
|
|
// Update the current selection, or clear if it is no longer the current object.
|
|
var selected = EventExecutor.GetEventHandler<ISelectHandler>(currentObject);
|
|
if (selected == ModuleController.eventSystem.currentSelectedGameObject) {
|
|
EventExecutor.Execute(ModuleController.eventSystem.currentSelectedGameObject, ModuleController.GetBaseEventData(),
|
|
ExecuteEvents.updateSelectedHandler);
|
|
} else {
|
|
ModuleController.eventSystem.SetSelectedGameObject(null, CurrentEventData);
|
|
}
|
|
|
|
// Execute hover event.
|
|
if (currentObject != null && currentObject == previousObject) {
|
|
EventExecutor.ExecuteHierarchy(currentObject, CurrentEventData, GvrExecuteEventsExtension.pointerHoverHandler);
|
|
}
|
|
}
|
|
|
|
private void UpdatePointer(GameObject previousObject) {
|
|
if (CurrentEventData == null) {
|
|
return;
|
|
}
|
|
|
|
GameObject currentObject = GetCurrentGameObject(); // Get the pointer target
|
|
bool isPointerActiveAndAvailable = IsPointerActiveAndAvailable();
|
|
|
|
bool isInteractive = CurrentEventData.pointerPress != null ||
|
|
EventExecutor.GetEventHandler<IPointerClickHandler>(currentObject) != null ||
|
|
EventExecutor.GetEventHandler<IDragHandler>(currentObject) != null;
|
|
|
|
if (isPointerHovering && currentObject != null && currentObject == previousObject) {
|
|
if (isPointerActiveAndAvailable) {
|
|
Pointer.OnPointerHover(CurrentEventData.pointerCurrentRaycast, isInteractive);
|
|
}
|
|
} else {
|
|
// If the object's don't match or the hovering object has been destroyed
|
|
// then the pointer has exited.
|
|
if (previousObject != null || (currentObject == null && isPointerHovering)) {
|
|
if (isPointerActiveAndAvailable) {
|
|
Pointer.OnPointerExit(previousObject);
|
|
}
|
|
isPointerHovering = false;
|
|
}
|
|
|
|
if (currentObject != null) {
|
|
if (isPointerActiveAndAvailable) {
|
|
Pointer.OnPointerEnter(CurrentEventData.pointerCurrentRaycast, isInteractive);
|
|
}
|
|
isPointerHovering = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold) {
|
|
if (!useDragThreshold)
|
|
return true;
|
|
|
|
return (pressPos - currentPos).sqrMagnitude >= threshold * threshold;
|
|
}
|
|
|
|
private void HandleDrag() {
|
|
bool moving = CurrentEventData.IsPointerMoving();
|
|
bool shouldStartDrag = ShouldStartDrag(CurrentEventData.pressPosition,
|
|
CurrentEventData.position,
|
|
ModuleController.eventSystem.pixelDragThreshold,
|
|
CurrentEventData.useDragThreshold);
|
|
|
|
if (moving && shouldStartDrag && CurrentEventData.pointerDrag != null && !CurrentEventData.dragging) {
|
|
EventExecutor.Execute(CurrentEventData.pointerDrag, CurrentEventData,
|
|
ExecuteEvents.beginDragHandler);
|
|
CurrentEventData.dragging = true;
|
|
}
|
|
|
|
// Drag notification
|
|
if (CurrentEventData.dragging && moving && CurrentEventData.pointerDrag != null) {
|
|
// Before doing drag we should cancel any pointer down state
|
|
// And clear selection!
|
|
if (CurrentEventData.pointerPress != CurrentEventData.pointerDrag) {
|
|
EventExecutor.Execute(CurrentEventData.pointerPress, CurrentEventData, ExecuteEvents.pointerUpHandler);
|
|
|
|
CurrentEventData.eligibleForClick = false;
|
|
CurrentEventData.pointerPress = null;
|
|
CurrentEventData.rawPointerPress = null;
|
|
}
|
|
|
|
EventExecutor.Execute(CurrentEventData.pointerDrag, CurrentEventData, ExecuteEvents.dragHandler);
|
|
}
|
|
}
|
|
|
|
private void HandlePendingClick() {
|
|
if (CurrentEventData == null || (!CurrentEventData.eligibleForClick && !CurrentEventData.dragging)) {
|
|
return;
|
|
}
|
|
|
|
if (IsPointerActiveAndAvailable()) {
|
|
Pointer.OnPointerClickUp();
|
|
}
|
|
|
|
var go = CurrentEventData.pointerCurrentRaycast.gameObject;
|
|
|
|
// Send pointer up and click events.
|
|
EventExecutor.Execute(CurrentEventData.pointerPress, CurrentEventData, ExecuteEvents.pointerUpHandler);
|
|
|
|
GameObject pointerClickHandler = EventExecutor.GetEventHandler<IPointerClickHandler>(go);
|
|
if (CurrentEventData.pointerPress == pointerClickHandler && CurrentEventData.eligibleForClick) {
|
|
EventExecutor.Execute(CurrentEventData.pointerPress, CurrentEventData, ExecuteEvents.pointerClickHandler);
|
|
}
|
|
|
|
if (CurrentEventData != null && CurrentEventData.pointerDrag != null && CurrentEventData.dragging) {
|
|
EventExecutor.ExecuteHierarchy(go, CurrentEventData, ExecuteEvents.dropHandler);
|
|
EventExecutor.Execute(CurrentEventData.pointerDrag, CurrentEventData, ExecuteEvents.endDragHandler);
|
|
}
|
|
|
|
if (CurrentEventData != null) {
|
|
// Clear the click state.
|
|
CurrentEventData.pointerPress = null;
|
|
CurrentEventData.rawPointerPress = null;
|
|
CurrentEventData.eligibleForClick = false;
|
|
CurrentEventData.clickCount = 0;
|
|
CurrentEventData.clickTime = 0;
|
|
CurrentEventData.pointerDrag = null;
|
|
CurrentEventData.dragging = false;
|
|
}
|
|
}
|
|
|
|
private void HandleTriggerDown() {
|
|
var go = CurrentEventData.pointerCurrentRaycast.gameObject;
|
|
|
|
// Send pointer down event.
|
|
CurrentEventData.pressPosition = CurrentEventData.position;
|
|
CurrentEventData.pointerPressRaycast = CurrentEventData.pointerCurrentRaycast;
|
|
CurrentEventData.pointerPress =
|
|
EventExecutor.ExecuteHierarchy(go, CurrentEventData, ExecuteEvents.pointerDownHandler) ??
|
|
EventExecutor.GetEventHandler<IPointerClickHandler>(go);
|
|
|
|
// Save the pending click state.
|
|
CurrentEventData.rawPointerPress = go;
|
|
CurrentEventData.eligibleForClick = true;
|
|
CurrentEventData.delta = Vector2.zero;
|
|
CurrentEventData.dragging = false;
|
|
CurrentEventData.useDragThreshold = true;
|
|
CurrentEventData.clickCount = 1;
|
|
CurrentEventData.clickTime = Time.unscaledTime;
|
|
|
|
// Save the drag handler as well
|
|
CurrentEventData.pointerDrag = EventExecutor.GetEventHandler<IDragHandler>(go);
|
|
if (CurrentEventData.pointerDrag != null) {
|
|
EventExecutor.Execute(CurrentEventData.pointerDrag, CurrentEventData, ExecuteEvents.initializePotentialDrag);
|
|
}
|
|
|
|
if (IsPointerActiveAndAvailable()) {
|
|
Pointer.OnPointerClickDown();
|
|
}
|
|
}
|
|
|
|
private GameObject GetCurrentGameObject() {
|
|
if (CurrentEventData != null) {
|
|
return CurrentEventData.pointerCurrentRaycast.gameObject;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Modified version of BaseInputModule.HandlePointerExitAndEnter that calls EventExecutor instead of
|
|
// UnityEngine.EventSystems.ExecuteEvents.
|
|
private void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget) {
|
|
// If we have no target or pointerEnter has been deleted then
|
|
// just send exit events to anything we are tracking.
|
|
// Afterwards, exit.
|
|
if (newEnterTarget == null || currentPointerData.pointerEnter == null) {
|
|
for (var i = 0; i < currentPointerData.hovered.Count; ++i) {
|
|
EventExecutor.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler);
|
|
}
|
|
|
|
currentPointerData.hovered.Clear();
|
|
|
|
if (newEnterTarget == null) {
|
|
currentPointerData.pointerEnter = newEnterTarget;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we have not changed hover target.
|
|
if (newEnterTarget && currentPointerData.pointerEnter == newEnterTarget) {
|
|
return;
|
|
}
|
|
|
|
GameObject commonRoot = ModuleController.FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget);
|
|
|
|
// We already an entered object from last time.
|
|
if (currentPointerData.pointerEnter != null) {
|
|
// Send exit handler call to all elements in the chain
|
|
// until we reach the new target, or null!
|
|
Transform t = currentPointerData.pointerEnter.transform;
|
|
|
|
while (t != null) {
|
|
// If we reach the common root break out!
|
|
if (commonRoot != null && commonRoot.transform == t)
|
|
break;
|
|
|
|
EventExecutor.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler);
|
|
currentPointerData.hovered.Remove(t.gameObject);
|
|
t = t.parent;
|
|
}
|
|
}
|
|
|
|
// Now issue the enter call up to but not including the common root.
|
|
currentPointerData.pointerEnter = newEnterTarget;
|
|
if (newEnterTarget != null) {
|
|
Transform t = newEnterTarget.transform;
|
|
|
|
while (t != null && t.gameObject != commonRoot) {
|
|
EventExecutor.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler);
|
|
currentPointerData.hovered.Add(t.gameObject);
|
|
t = t.parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TryExitPointer() {
|
|
if (Pointer == null) {
|
|
return;
|
|
}
|
|
|
|
GameObject currentGameObject = GetCurrentGameObject();
|
|
if (currentGameObject) {
|
|
Pointer.OnPointerExit(currentGameObject);
|
|
}
|
|
}
|
|
|
|
private bool IsPointerActiveAndAvailable() {
|
|
return pointer != null && pointer.IsAvailable;
|
|
}
|
|
|
|
private void RaycastAll() {
|
|
ModuleController.RaycastResultCache.Clear();
|
|
ModuleController.eventSystem.RaycastAll(CurrentEventData, ModuleController.RaycastResultCache);
|
|
}
|
|
}
|