FittsLaw/Assets/GoogleVR/Scripts/EventSystem/InputModule/GvrPointerInputModuleImpl.cs

459 lines
17 KiB
C#
Raw Permalink Normal View History

2018-10-08 23:54:11 -04:00
// 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);
}
}