VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Tests/Runtime/UIPointerTests.cs
2025-07-29 13:45:50 +03:00

2043 lines
100 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.TestTools;
using UnityEngine.UI;
using UnityEngine.XR.Interaction.Toolkit.UI;
using Unity.XR.CoreUtils;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
using UnityEditor;
namespace UnityEngine.XR.Interaction.Toolkit.Tests
{
static class XRControllerRecorderExtensions
{
internal static void SetNextPose(this XRControllerRecorder recorder, Vector3 position, Quaternion rotation, bool selectActive, bool activateActive, bool pressActive)
{
var currentRecording = recorder.recording;
currentRecording.InitRecording();
currentRecording.AddRecordingFrameNonAlloc(new XRControllerState(0.0f, position, rotation, InputTrackingState.All, true, selectActive, activateActive, pressActive));
currentRecording.AddRecordingFrameNonAlloc(new XRControllerState(1000f, position, rotation, InputTrackingState.All, true, selectActive, activateActive, pressActive));
recorder.recording = currentRecording;
recorder.ResetPlayback();
recorder.isPlaying = true;
}
}
[TestFixture]
class UIPointerTests : InputTestFixture
{
// current activation depth percentage in the XRPokeLogic code - 5% to catch depth properly
const float k_DepthPercentActivationThreshold = 0.020f;
const float k_DropdownItemHeight = -0.045f;
internal enum EventType
{
Click,
Down,
Up,
Enter,
Exit,
Select,
Deselect,
PotentialDrag,
BeginDrag,
Dragging,
Drop,
EndDrag,
Move,
Submit,
Cancel,
Scroll,
UpdateSelected,
#if UNITY_2021_1_OR_NEWER
PointerMove,
#endif
}
struct TestObjects
{
public Camera camera;
public TestEventSystem eventSystem;
public XRControllerRecorder controllerRecorder;
public XRRayInteractor interactor;
public UICallbackReceiver leftUIReceiver;
public UICallbackReceiver rightUIReceiver;
public GlobalUIReceiver globalUIReceiver;
public XRUIInputModule uiInputModule;
public GameObject canvasGameObject;
// *Note: Copied from InputSystem\UI\Plugins\UITests.cs
// Assume a 640x480 resolution and translate the given coordinates from a resolution
// in that space to coordinates in the current camera screen space.
public Vector2 From640x480ToScreen(float x, float y)
{
var cameraRect = camera.rect;
var cameraPixelRect = camera.pixelRect;
var result = new Vector2(cameraPixelRect.x + x / 640f * cameraRect.width * cameraPixelRect.width,
cameraPixelRect.y + y / 480f * cameraRect.height * cameraPixelRect.height);
// Pixel-snap. Not sure where this is coming from but Mac tests are failing without this.
return new Vector2(Mathf.Floor(result.x), Mathf.Floor(result.y));
}
public void ClearEvents()
{
globalUIReceiver.Reset();
leftUIReceiver.Reset();
rightUIReceiver.Reset();
}
public void ResetInputActions()
{
if (uiInputModule != null)
{
uiInputModule.pointAction = null;
uiInputModule.leftClickAction = null;
uiInputModule.middleClickAction = null;
uiInputModule.rightClickAction = null;
uiInputModule.scrollWheelAction = null;
uiInputModule.navigateAction = null;
uiInputModule.submitAction = null;
uiInputModule.cancelAction = null;
}
}
}
internal struct Event
{
public EventType type;
public BaseEventData data;
public GameObject target;
public Event(EventType type, BaseEventData data, GameObject target = null)
{
this.type = type;
this.data = data;
this.target = target;
}
public override string ToString()
{
var dataString = data.ToString();
dataString = dataString.Replace("\n", "\n\t");
return $"{type} - {target}[\n\t{dataString}]";
}
}
static BaseEventData CloneEventData(BaseEventData eventData)
{
switch (eventData)
{
case AxisEventData axisEventData:
return new AxisEventData(EventSystem.current)
{
moveVector = axisEventData.moveVector,
moveDir = axisEventData.moveDir,
};
case TrackedDeviceEventData trackedEventData:
return new TrackedDeviceEventData(EventSystem.current)
{
pointerId = trackedEventData.pointerId,
position = trackedEventData.position,
button = trackedEventData.button,
clickCount = trackedEventData.clickCount,
clickTime = trackedEventData.clickTime,
eligibleForClick = trackedEventData.eligibleForClick,
delta = trackedEventData.delta,
scrollDelta = trackedEventData.scrollDelta,
dragging = trackedEventData.dragging,
hovered = new List<GameObject>(trackedEventData.hovered),
pointerDrag = trackedEventData.pointerDrag,
pointerEnter = trackedEventData.pointerEnter,
pointerPress = trackedEventData.pointerPress,
pressPosition = trackedEventData.pressPosition,
pointerCurrentRaycast = trackedEventData.pointerCurrentRaycast,
pointerPressRaycast = trackedEventData.pointerPressRaycast,
rawPointerPress = trackedEventData.rawPointerPress,
useDragThreshold = trackedEventData.useDragThreshold,
layerMask = trackedEventData.layerMask,
rayHitIndex = trackedEventData.rayHitIndex,
rayPoints = new List<Vector3>(trackedEventData.rayPoints),
};
case PointerEventData pointerEventData:
return new PointerEventData(EventSystem.current)
{
pointerId = pointerEventData.pointerId,
position = pointerEventData.position,
button = pointerEventData.button,
clickCount = pointerEventData.clickCount,
clickTime = pointerEventData.clickTime,
eligibleForClick = pointerEventData.eligibleForClick,
delta = pointerEventData.delta,
scrollDelta = pointerEventData.scrollDelta,
dragging = pointerEventData.dragging,
hovered = new List<GameObject>(pointerEventData.hovered),
pointerDrag = pointerEventData.pointerDrag,
pointerEnter = pointerEventData.pointerEnter,
pointerPress = pointerEventData.pointerPress,
pressPosition = pointerEventData.pressPosition,
pointerCurrentRaycast = pointerEventData.pointerCurrentRaycast,
pointerPressRaycast = pointerEventData.pointerPressRaycast,
rawPointerPress = pointerEventData.rawPointerPress,
useDragThreshold = pointerEventData.useDragThreshold,
};
default:
return new BaseEventData(EventSystem.current);
}
}
internal class UICallbackReceiver : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerEnterHandler,
IPointerExitHandler, IPointerUpHandler, IMoveHandler, ISelectHandler, IDeselectHandler, IInitializePotentialDragHandler,
IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, ISubmitHandler, ICancelHandler, IScrollHandler
#if UNITY_2021_1_OR_NEWER
, IPointerMoveHandler
#endif
{
public List<Event> events = new List<Event>();
public void Reset()
{
events.Clear();
}
public void OnPointerClick(PointerEventData eventData)
{
events.Add(new Event(EventType.Click, CloneEventData(eventData)));
}
public void OnPointerDown(PointerEventData eventData)
{
events.Add(new Event(EventType.Down, CloneEventData(eventData)));
}
public void OnPointerEnter(PointerEventData eventData)
{
events.Add(new Event(EventType.Enter, CloneEventData(eventData)));
}
public void OnPointerExit(PointerEventData eventData)
{
events.Add(new Event(EventType.Exit, CloneEventData(eventData)));
}
public void OnPointerUp(PointerEventData eventData)
{
events.Add(new Event(EventType.Up, CloneEventData(eventData)));
}
#if UNITY_2021_1_OR_NEWER
public void OnPointerMove(PointerEventData eventData)
{
events.Add(new Event(EventType.PointerMove, CloneEventData(eventData)));
}
#endif
public void OnMove(AxisEventData eventData)
{
events.Add(new Event(EventType.Move, CloneEventData(eventData)));
}
public void OnSubmit(BaseEventData eventData)
{
events.Add(new Event(EventType.Submit, CloneEventData(eventData)));
}
public void OnCancel(BaseEventData eventData)
{
events.Add(new Event(EventType.Cancel, CloneEventData(eventData)));
}
public void OnSelect(BaseEventData eventData)
{
events.Add(new Event(EventType.Select, CloneEventData(eventData)));
}
public void OnDeselect(BaseEventData eventData)
{
events.Add(new Event(EventType.Deselect, CloneEventData(eventData)));
}
public void OnInitializePotentialDrag(PointerEventData eventData)
{
events.Add(new Event(EventType.PotentialDrag, CloneEventData(eventData)));
}
public void OnBeginDrag(PointerEventData eventData)
{
events.Add(new Event(EventType.BeginDrag, CloneEventData(eventData)));
}
public void OnDrag(PointerEventData eventData)
{
events.Add(new Event(EventType.Dragging, CloneEventData(eventData)));
}
public void OnDrop(PointerEventData eventData)
{
events.Add(new Event(EventType.Drop, CloneEventData(eventData)));
}
public void OnEndDrag(PointerEventData eventData)
{
events.Add(new Event(EventType.EndDrag, CloneEventData(eventData)));
}
public void OnScroll(PointerEventData eventData)
{
events.Add(new Event(EventType.Scroll, CloneEventData(eventData)));
}
}
class GlobalUIReceiver
{
public List<Event> events = new List<Event>();
public GlobalUIReceiver(UIInputModule module)
{
// We never unsubscribe these events--Always ensure only one GlobalUIReceiver is associated with one UIInputModule
module.pointerEnter += OnPointerEnter;
module.pointerExit += OnPointerExit;
module.pointerDown += OnPointerDown;
module.pointerUp += OnPointerUp;
module.pointerClick += OnPointerClick;
#if UNITY_2021_1_OR_NEWER
module.pointerMove += OnPointerMove;
#endif
module.initializePotentialDrag += OnInitializePotentialDrag;
module.beginDrag += OnBeginDrag;
module.drag += OnDrag;
module.endDrag += OnEndDrag;
module.drop += OnDrop;
module.scroll += OnScroll;
module.updateSelected += OnUpdateSelected;
module.move += OnMove;
module.submit += OnSubmit;
module.cancel += OnCancel;
}
public void Reset()
{
events.Clear();
}
void OnPointerEnter(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Enter, CloneEventData(eventData), target));
}
void OnPointerExit(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Exit, CloneEventData(eventData), target));
}
void OnPointerDown(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Down, CloneEventData(eventData), target));
}
void OnPointerUp(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Up, CloneEventData(eventData), target));
}
void OnPointerClick(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Click, CloneEventData(eventData), target));
}
#if UNITY_2021_1_OR_NEWER
public void OnPointerMove(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.PointerMove, CloneEventData(eventData)));
}
#endif
void OnInitializePotentialDrag(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.PotentialDrag, CloneEventData(eventData), target));
}
void OnBeginDrag(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.BeginDrag, CloneEventData(eventData), target));
}
void OnDrag(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Dragging, CloneEventData(eventData), target));
}
void OnEndDrag(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.EndDrag, CloneEventData(eventData), target));
}
void OnDrop(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Drop, CloneEventData(eventData), target));
}
void OnScroll(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Scroll, CloneEventData(eventData), target));
}
void OnUpdateSelected(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.UpdateSelected, CloneEventData(eventData), target));
}
void OnMove(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Move, CloneEventData(eventData), target));
}
void OnSubmit(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Submit, CloneEventData(eventData), target));
}
void OnCancel(GameObject target, BaseEventData eventData)
{
events.Add(new Event(EventType.Cancel, CloneEventData(eventData), target));
}
}
internal class TestEventSystem : EventSystem
{
public void InvokeUpdate()
{
current = this; // Needs to be current to be allowed to update.
Update();
}
}
static TestObjects SetupRig(bool setFirstSelected = false)
{
var testObjects = new TestObjects();
_ = new GameObject("InteractionManager", typeof(XRInteractionManager));
var rigGo = new GameObject("XROrigin");
rigGo.SetActive(false);
var rig = rigGo.AddComponent<XROrigin>();
// Add camera offset
var cameraOffsetGo = new GameObject();
cameraOffsetGo.name = "CameraOffset";
cameraOffsetGo.transform.SetParent(rig.transform, false);
rig.CameraFloorOffsetObject = cameraOffsetGo;
// Set up camera and canvas on which we can perform ray casts.
var cameraGo = new GameObject("Camera");
cameraGo.transform.parent = rigGo.transform;
Camera camera = testObjects.camera = cameraGo.AddComponent<Camera>();
camera.stereoTargetEye = StereoTargetEyeMask.None;
camera.pixelRect = new Rect(0, 0, 640, 480);
rig.Camera = cameraGo.GetComponent<Camera>();
rigGo.SetActive(true);
var eventSystemGo = new GameObject("EventSystem", typeof(TestEventSystem), typeof(XRUIInputModule));
var inputModule = eventSystemGo.GetComponent<XRUIInputModule>();
inputModule.uiCamera = camera;
inputModule.enableXRInput = true;
inputModule.enableMouseInput = false;
inputModule.enableTouchInput = false;
inputModule.enableGamepadInput = false;
inputModule.enableJoystickInput = false;
testObjects.eventSystem = eventSystemGo.GetComponent<TestEventSystem>();
testObjects.eventSystem.UpdateModules();
if (!setFirstSelected) // This will get called from SetupUIScene
testObjects.eventSystem.InvokeUpdate(); // Initial update only sets current module.
testObjects.globalUIReceiver = new GlobalUIReceiver(inputModule);
var interactorGo = new GameObject("Interactor", typeof(XRRayInteractor), typeof(XRControllerRecorder));
interactorGo.transform.parent = rigGo.transform;
var rayInteractor = interactorGo.GetComponent<XRRayInteractor>();
testObjects.controllerRecorder = interactorGo.GetComponent<XRControllerRecorder>();
testObjects.controllerRecorder.recording = ScriptableObject.CreateInstance<XRControllerRecording>();
testObjects.controllerRecorder.SetInteractor(rayInteractor);
testObjects.interactor = rayInteractor;
testObjects.interactor.maxRaycastDistance = int.MaxValue;
testObjects.interactor.referenceFrame = rigGo.transform;
testObjects.uiInputModule = inputModule;
return testObjects;
}
static TestObjects SetupUIScene(bool setFirstSelected = false, bool isWorldSpace = false)
{
var testObjects = SetupRig(setFirstSelected);
testObjects.canvasGameObject = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(TrackedDeviceGraphicRaycaster));
var canvas = testObjects.canvasGameObject.GetComponent<Canvas>();
canvas.worldCamera = testObjects.camera;
canvas.renderMode = isWorldSpace ? RenderMode.WorldSpace : RenderMode.ScreenSpaceCamera;
// Set up a GameObject hierarchy that we send events to. In a real setup,
// this would be a hierarchy involving UI components.
var parentGameObject = new GameObject("Parent");
var parentTransform = parentGameObject.AddComponent<RectTransform>();
parentGameObject.AddComponent<UICallbackReceiver>();
var leftChildGameObject = new GameObject("Left Child");
var leftChildTransform = leftChildGameObject.AddComponent<RectTransform>();
leftChildGameObject.AddComponent<Image>();
testObjects.leftUIReceiver = leftChildGameObject.AddComponent<UICallbackReceiver>();
leftChildGameObject.AddComponent<Selectable>();
var rightChildGameObject = new GameObject("Right Child");
var rightChildTransform = rightChildGameObject.AddComponent<RectTransform>();
rightChildGameObject.AddComponent<Image>();
testObjects.rightUIReceiver = rightChildGameObject.AddComponent<UICallbackReceiver>();
rightChildGameObject.AddComponent<Selectable>();
parentTransform.SetParent(canvas.transform, false);
leftChildTransform.SetParent(parentTransform, false);
rightChildTransform.SetParent(parentTransform, false);
if (setFirstSelected)
{
testObjects.eventSystem.firstSelectedGameObject = leftChildGameObject;
testObjects.eventSystem.InvokeUpdate();
}
// Parent occupies full space of canvas.
parentTransform.sizeDelta = new Vector2(640, 480);
// Left child occupies left half of parent.
const int quarterSize = 640 / 4;
leftChildTransform.anchoredPosition = new Vector2(-quarterSize, 0);
leftChildTransform.sizeDelta = new Vector2(320, 480);
// Right child occupies right half of parent.
rightChildTransform.anchoredPosition = new Vector2(quarterSize, 0);
rightChildTransform.sizeDelta = new Vector2(320, 480);
// If canvas is in world-space, move it back 200 units to stay in view of the world-space camera
if (isWorldSpace)
canvas.transform.Translate(0.0f, 0.0f, 200.0f);
return testObjects;
}
static TestObjects SetupPhysicsScene()
{
var testObjects = SetupRig();
var physicsRaycaster = new GameObject("PhysicsRaycaster", typeof(TrackedDevicePhysicsRaycaster)).GetComponent<TrackedDevicePhysicsRaycaster>();
physicsRaycaster.SetEventCamera(testObjects.camera);
var parentGameObject = new GameObject("Interactables");
var parentTransform = parentGameObject.transform;
var groupGameObject = new GameObject("Group");
var groupTransform = groupGameObject.transform;
groupGameObject.AddComponent<UICallbackReceiver>();
var leftGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
testObjects.leftUIReceiver = leftGameObject.AddComponent<UICallbackReceiver>();
var leftTransform = leftGameObject.transform;
leftTransform.position = new Vector3(-0.5f, 0.0f, 1.75f);
leftGameObject.AddComponent<Selectable>();
var rightGameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
testObjects.rightUIReceiver = rightGameObject.AddComponent<UICallbackReceiver>();
var rightTransform = rightGameObject.transform;
rightTransform.position = new Vector3(0.5f, 0.0f, 1.75f);
rightGameObject.AddComponent<Selectable>();
groupGameObject.transform.SetParent(parentTransform, false);
leftGameObject.transform.SetParent(groupTransform, false);
rightGameObject.transform.SetParent(groupTransform, false);
return testObjects;
}
static void ResetReceivers(TestObjects testObjects)
{
testObjects.ClearEvents();
Assert.That(testObjects.leftUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(testObjects.rightUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(testObjects.globalUIReceiver.events, Has.Count.EqualTo(0));
}
static IEnumerator ResetTestObjects(TestObjects testObjects)
{
testObjects.controllerRecorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -90.0f, 0.0f), false, false, false);
testObjects.eventSystem.SetSelectedGameObject(null);
yield return new WaitForFixedUpdate();
yield return null;
ResetReceivers(testObjects);
}
static IEnumerator CheckEvents(TestObjects testObjects)
{
var leftUIReceiver = testObjects.leftUIReceiver;
var rightUIReceiver = testObjects.rightUIReceiver;
var globalUIReceiver = testObjects.globalUIReceiver;
var recorder = testObjects.controllerRecorder;
var eventSystem = testObjects.eventSystem;
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(testObjects.interactor, out var model), Is.True);
var primaryPointerId = model.pointerId;
Assert.That(primaryPointerId, Is.Not.LessThan(0));
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.False);
// Reset to Defaults
yield return ResetTestObjects(testObjects);
// Move over left child.
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, false);
yield return null;
var globalEvents = globalUIReceiver.events;
var leftUIReceiverParentTransform = leftUIReceiver.transform.parent;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents, Has.Count.EqualTo(6));
Assert.That(globalEvents[0].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents[0].target, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(globalEvents[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[2].target, Is.EqualTo(leftUIReceiverParentTransform.gameObject));
Assert.That(globalEvents[3].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[4].target, Is.EqualTo(leftUIReceiverParentTransform.parent.gameObject));
Assert.That(globalEvents[5].type, Is.EqualTo(EventType.PointerMove));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents, Has.Count.EqualTo(3));
Assert.That(globalEvents[0].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents[0].target, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(globalEvents[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[1].target, Is.EqualTo(leftUIReceiverParentTransform.gameObject));
Assert.That(globalEvents[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[2].target, Is.EqualTo(leftUIReceiverParentTransform.parent.gameObject));
#endif
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
var eventData = (TrackedDeviceEventData)leftUIReceiver.events[0].data;
Assert.That(eventData.interactor, Is.EqualTo(testObjects.interactor));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
ResetReceivers(testObjects);
// Check basic down/up
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, true);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Select));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, false);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Click));
Assert.That(((PointerEventData)leftUIReceiver.events[1].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Click));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.UpdateSelected));
yield return ResetTestObjects(testObjects);
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.False);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.False);
// Check down, off, back on, and up
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, true);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Select));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(((PointerEventData)leftUIReceiver.events[2].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, 30.0f, 0.0f), false, false, true);
yield return null;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Exit));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Dragging));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(((PointerEventData)rightUIReceiver.events[0].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(rightUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(9));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[6].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(globalUIReceiver.events[7].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events[8].type, Is.EqualTo(EventType.UpdateSelected));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Exit));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.Dragging));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(((PointerEventData)rightUIReceiver.events[0].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(5));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.UpdateSelected));
#endif
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
ResetReceivers(testObjects);
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, false);
yield return null;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(5));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Click));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.EndDrag));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(((PointerEventData)leftUIReceiver.events[3].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(leftUIReceiver.events[4].type, Is.EqualTo(EventType.PointerMove));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(rightUIReceiver.events[1].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(10));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Click));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.EndDrag));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[6].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events[7].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[8].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[9].type, Is.EqualTo(EventType.UpdateSelected));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Click));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.EndDrag));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(((PointerEventData)leftUIReceiver.events[3].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Click));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.EndDrag));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
#endif
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
yield return ResetTestObjects(testObjects);
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.False);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.False);
// Check down and drag
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, true);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Select));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(((PointerEventData)leftUIReceiver.events[2].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
// Move to new location on left child
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -10.0f, 0.0f), false, false, true);
yield return null;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.BeginDrag));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.UpdateSelected));
#endif
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
ResetReceivers(testObjects);
// Move children
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, 30.0f, 0.0f), false, false, true);
yield return null;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Exit));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.Dragging));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(rightUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(8));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[6].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events[7].type, Is.EqualTo(EventType.UpdateSelected));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Exit));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Dragging));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Exit));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.Dragging));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.UpdateSelected));
#endif
ResetReceivers(testObjects);
// Deselect
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, 30.0f, 0.0f), false, false, false);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.EndDrag));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Drop));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Drop));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.EndDrag));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
}
[UnityTest]
public IEnumerator TrackedDevicesCanDriveUIGraphics()
{
var testObjects = SetupUIScene();
yield return CheckEvents(testObjects);
// This suppresses a warning that would be logged by TrackedDeviceGraphicRaycaster if the Camera is destroyed first
Object.Destroy(testObjects.eventSystem.gameObject);
}
[UnityTest]
public IEnumerator TrackedDevicesCanDriveUIPhysics()
{
var testObjects = SetupPhysicsScene();
yield return CheckEvents(testObjects);
// This suppresses a warning that would be logged by TrackedDeviceGraphicRaycaster if the Camera is destroyed first
Object.Destroy(testObjects.eventSystem.gameObject);
}
[UnityTest]
public IEnumerator HeadMovementDoesNotAffectTrackedDeviceDragEvents()
{
var testObjects = SetupUIScene(isWorldSpace: true);
var leftUIReceiver = testObjects.leftUIReceiver;
var rightUIReceiver = testObjects.rightUIReceiver;
var globalUIReceiver = testObjects.globalUIReceiver;
var recorder = testObjects.controllerRecorder;
var headCamera = testObjects.camera;
var eventSystem = testObjects.eventSystem;
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(testObjects.interactor, out var model), Is.True);
var primaryPointerId = model.pointerId;
Assert.That(primaryPointerId, Is.Not.LessThan(0));
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.False);
// Reset to Defaults
yield return ResetTestObjects(testObjects);
// Move over left child.
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, false);
yield return null;
Assert.That(leftUIReceiver.events.Select(e => e.type), Is.EqualTo(new[]
{
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
}));
Assert.That(rightUIReceiver.events, Is.Empty);
Assert.That(globalUIReceiver.events.Select(e => e.type), Is.EqualTo(new[]
{
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
}));
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
// Stay still and activate button press.
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, true);
yield return null;
Assert.That(leftUIReceiver.events.Select(e => e.type), Is.EqualTo(new[]
{
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
EventType.Down,
EventType.Select,
EventType.PotentialDrag,
}));
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(((PointerEventData)leftUIReceiver.events[2].data).pointerId, Is.EqualTo(primaryPointerId));
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(eventSystem.IsPointerOverGameObject(-1), Is.True);
Assert.That(rightUIReceiver.events, Is.Empty);
Assert.That(globalUIReceiver.events.Select(e => e.type), Is.EqualTo(new[]
{
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
EventType.Enter,
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
#endif
EventType.Down,
EventType.PotentialDrag,
EventType.UpdateSelected,
}));
ResetReceivers(testObjects);
// Rotate head instead of controller. Controller must move very slightly to trigger processing by UIInputModule, but not enough to count as a drag.
headCamera.transform.Rotate(0.0f, 10.0f, 0.0f);
recorder.SetNextPose(new Vector3(0.0f, 0.0f, 0.01f), Quaternion.Euler(0.0f, -30.1f, 0.0f), false, false, true);
yield return null;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events.Select(e => e.type), Is.EqualTo(new[] { EventType.PointerMove }));
#else
Assert.That(leftUIReceiver.events, Is.Empty);
#endif
Assert.That(eventSystem.currentSelectedGameObject, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(eventSystem.IsPointerOverGameObject(primaryPointerId), Is.True);
Assert.That(rightUIReceiver.events, Is.Empty);
Assert.That(globalUIReceiver.events.Select(e => e.type), Is.EqualTo(new[]
{
#if UNITY_2021_1_OR_NEWER
EventType.PointerMove,
EventType.PointerMove,
EventType.PointerMove,
#endif
EventType.UpdateSelected,
}));
ResetReceivers(testObjects);
}
[UnityTest]
public IEnumerator PointerEnterBubblesUp()
{
var testObjects = SetupPhysicsScene();
var leftUIReceiver = testObjects.leftUIReceiver;
var rightUIReceiver = testObjects.rightUIReceiver;
var globalUIReceiver = testObjects.globalUIReceiver;
// Have the event receiver on a parent of the Collider child
// to test that pointer enter events are bubbled up to parent objects
// even when the hit GameObject does not have any event handlers itself.
Object.Destroy(leftUIReceiver.GetComponent<Collider>());
var leftUIColliderGameObject = new GameObject("Collider", typeof(BoxCollider));
leftUIColliderGameObject.transform.SetParent(leftUIReceiver.transform, false);
var recorder = testObjects.controllerRecorder;
// Reset to Defaults
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -90.0f, 0.0f), false, false, false);
yield return new WaitForFixedUpdate();
yield return null;
ResetReceivers(testObjects);
// Move over left child.
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -30.0f, 0.0f), false, false, false);
yield return null;
var globalEvents = globalUIReceiver.events;
var leftUIReceiverParentTransform = leftUIReceiver.transform.parent;
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents, Has.Count.EqualTo(8));
Assert.That(globalEvents[0].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents[0].target, Is.EqualTo(leftUIColliderGameObject));
Assert.That(globalEvents[1].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[2].target, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(globalEvents[3].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents[4].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[4].target, Is.EqualTo(leftUIReceiverParentTransform.gameObject));
Assert.That(globalEvents[5].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalEvents[6].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[6].target, Is.EqualTo(leftUIReceiverParentTransform.parent.gameObject));
Assert.That(globalEvents[7].type, Is.EqualTo(EventType.PointerMove));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Enter));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents, Has.Count.EqualTo(4));
Assert.That(globalEvents[0].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents[0].target, Is.EqualTo(leftUIColliderGameObject));
Assert.That(globalEvents[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[1].target, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(globalEvents[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[2].target, Is.EqualTo(leftUIReceiverParentTransform.gameObject));
Assert.That(globalEvents[3].type, Is.EqualTo(EventType.Enter));
Assert.That(globalEvents[3].target, Is.EqualTo(leftUIReceiverParentTransform.parent.gameObject));
#endif
var eventData = (TrackedDeviceEventData)leftUIReceiver.events[0].data;
Assert.That(eventData.interactor, Is.EqualTo(testObjects.interactor));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
ResetReceivers(testObjects);
// Move off left child.
recorder.SetNextPose(Vector3.zero, Quaternion.Euler(0.0f, -90.0f, 0.0f), false, false, false);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Exit));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents, Has.Count.EqualTo(4));
Assert.That(globalEvents[0].type, Is.EqualTo(EventType.Exit));
Assert.That(globalEvents[0].data, Is.TypeOf<TrackedDeviceEventData>());
Assert.That(globalEvents[0].target, Is.EqualTo(leftUIColliderGameObject));
Assert.That(globalEvents[1].type, Is.EqualTo(EventType.Exit));
Assert.That(globalEvents[1].target, Is.EqualTo(leftUIReceiver.gameObject));
Assert.That(globalEvents[2].type, Is.EqualTo(EventType.Exit));
Assert.That(globalEvents[2].target, Is.EqualTo(leftUIReceiverParentTransform.gameObject));
Assert.That(globalEvents[3].type, Is.EqualTo(EventType.Exit));
Assert.That(globalEvents[3].target, Is.EqualTo(leftUIReceiverParentTransform.parent.gameObject));
// This suppresses a warning that would be logged by TrackedDeviceGraphicRaycaster if the Camera is destroyed first
Object.Destroy(testObjects.eventSystem.gameObject);
}
[UnityTest]
public IEnumerator UIJoystickNavigation()
{
var testObjects = SetupUIScene(true);
// Enable device input
testObjects.uiInputModule.enableXRInput = false;
testObjects.uiInputModule.enableGamepadInput = false;
testObjects.uiInputModule.enableJoystickInput = true;
testObjects.uiInputModule.enableBuiltinActionsAsFallback = false;
var joystick = InputSystem.InputSystem.AddDevice<Joystick>();
joystick.MakeCurrent();
// Setup joystick input with new input system
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
// Create actions.
var map = new InputActionMap("UI Map");
asset.AddActionMap(map);
var navigateAction = map.AddAction("Navigate", type: InputActionType.Value);
var submitAction = map.AddAction("Submit", type: InputActionType.Button);
var cancelAction = map.AddAction("Cancel", type: InputActionType.Button);
// Create bindings.
navigateAction.AddBinding(joystick.stick);
submitAction.AddBinding(joystick.trigger);
// Associate to UIInputModule
testObjects.uiInputModule.navigateAction = InputActionReference.Create(navigateAction);
testObjects.uiInputModule.submitAction = InputActionReference.Create(submitAction);
testObjects.uiInputModule.cancelAction = InputActionReference.Create(cancelAction);
map.Enable();
// We can pass null into the cancelButton field of this function since it is not explicitly defined in the Joystick class.
yield return InputDeviceUINavigationChecks(testObjects, joystick, joystick.stick, joystick.trigger, null);
}
[UnityTest]
public IEnumerator UIJoystickNavigationUsingFallback()
{
var testObjects = SetupUIScene(true);
// Enable device input
testObjects.uiInputModule.enableXRInput = false;
testObjects.uiInputModule.enableGamepadInput = false;
testObjects.uiInputModule.enableJoystickInput = true;
testObjects.uiInputModule.enableBuiltinActionsAsFallback = true;
testObjects.ResetInputActions();
var joystick = InputSystem.InputSystem.AddDevice<Joystick>();
joystick.MakeCurrent();
// We can pass null into the cancelButton field of this function since it is not explicitly defined in the Joystick class.
yield return InputDeviceUINavigationChecks(testObjects, joystick, joystick.stick, joystick.trigger, null);
}
[UnityTest]
public IEnumerator UIGamepadNavigation()
{
var testObjects = SetupUIScene(true);
// Enable device input
testObjects.uiInputModule.enableXRInput = false;
testObjects.uiInputModule.enableGamepadInput = true;
testObjects.uiInputModule.enableJoystickInput = false;
testObjects.uiInputModule.enableBuiltinActionsAsFallback = false;
var gamepad = InputSystem.InputSystem.AddDevice<Gamepad>();
gamepad.MakeCurrent();
// Setup gamepad input with new input system
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
// Create actions.
var map = new InputActionMap("UI Map");
asset.AddActionMap(map);
var navigateAction = map.AddAction("Navigate", type: InputActionType.Value);
var submitAction = map.AddAction("Submit", type: InputActionType.Button);
var cancelAction = map.AddAction("Cancel", type: InputActionType.Button);
// Create bindings.
navigateAction.AddBinding(gamepad.leftStick);
submitAction.AddBinding(gamepad.buttonSouth);
cancelAction.AddBinding(gamepad.buttonEast);
// Associate to UIInputModule
testObjects.uiInputModule.navigateAction = InputActionReference.Create(navigateAction);
testObjects.uiInputModule.submitAction = InputActionReference.Create(submitAction);
testObjects.uiInputModule.cancelAction = InputActionReference.Create(cancelAction);
map.Enable();
yield return InputDeviceUINavigationChecks(testObjects, gamepad, gamepad.leftStick, gamepad.buttonSouth, gamepad.buttonEast);
}
[UnityTest]
public IEnumerator UIGamepadNavigationUsingFallback()
{
var testObjects = SetupUIScene(true);
// Enable device input
testObjects.uiInputModule.enableXRInput = false;
testObjects.uiInputModule.enableGamepadInput = true;
testObjects.uiInputModule.enableJoystickInput = false;
testObjects.uiInputModule.enableBuiltinActionsAsFallback = true;
testObjects.ResetInputActions();
var gamepad = InputSystem.InputSystem.AddDevice<Gamepad>();
gamepad.MakeCurrent();
yield return InputDeviceUINavigationChecks(testObjects, gamepad, gamepad.leftStick, gamepad.buttonSouth, gamepad.buttonEast);
}
IEnumerator InputDeviceUINavigationChecks(TestObjects testObjects, InputSystem.InputDevice device, StickControl stick, ButtonControl submitButton, ButtonControl cancelButton)
{
// First we check inputs here to make sure we are working with populated fields
// The cancelButton is optional since the Joystick class does not have it, so skip that check and handle below
Assert.That(device, Is.Not.Null);
Assert.That(stick, Is.Not.Null);
Assert.That(submitButton, Is.Not.Null);
#pragma warning disable CS0618 // Type or member is obsolete -- For backwards compatibility with existing projects
testObjects.uiInputModule.activeInputMode = XRUIInputModule.ActiveInputMode.InputSystemActions;
#pragma warning restore CS0618
if (device is Gamepad)
Assert.That(Gamepad.current, Is.SameAs(device));
else if (device is Joystick)
Assert.That(Joystick.current, Is.SameAs(device));
var leftUIReceiver = testObjects.leftUIReceiver;
var rightUIReceiver = testObjects.rightUIReceiver;
var globalUIReceiver = testObjects.globalUIReceiver;
// Set left object as selected
testObjects.eventSystem.SetSelectedGameObject(leftUIReceiver.gameObject);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Select));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(0));
Assert.That(testObjects.eventSystem.currentSelectedGameObject, Is.SameAs(leftUIReceiver.gameObject));
ResetReceivers(testObjects);
// Move right on gamepad.
Set(stick, Vector2.right);
yield return null;
// should have moved from left to right object.
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Move));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Deselect));
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Select));
Assert.That(testObjects.eventSystem.currentSelectedGameObject, Is.SameAs(rightUIReceiver.gameObject));
ResetReceivers(testObjects);
// Check Submit button press
Set(stick, Vector2.zero);
Set(submitButton, 1);
yield return null;
Set(submitButton, 0);
yield return null;
// make sure only right is receiving submit
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Submit));
Assert.That(leftUIReceiver.events, Is.Empty);
ResetReceivers(testObjects);
if (cancelButton != null)
{
// Check Cancel button press
Set(stick, Vector2.zero);
Set(cancelButton, 1);
yield return null;
Set(cancelButton, 0);
yield return null;
// make sure only right is receiving cancel
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Cancel));
Assert.That(leftUIReceiver.events, Is.Empty);
ResetReceivers(testObjects);
}
// Move back to left on gamepad.
Set(stick, Vector2.left);
yield return null;
// should have moved from left to right object.
Assert.That(rightUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(rightUIReceiver.events[0].type, Is.EqualTo(EventType.Move));
Assert.That(rightUIReceiver.events[1].type, Is.EqualTo(EventType.Deselect));
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Select));
Assert.That(testObjects.eventSystem.currentSelectedGameObject, Is.SameAs(leftUIReceiver.gameObject));
ResetReceivers(testObjects);
// Check Submit button pressed
Set(stick, Vector2.zero);
Set(submitButton, 1);
yield return null;
Set(submitButton, 0);
yield return null;
// Make sure only left is receiving submit
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Submit));
Assert.That(rightUIReceiver.events, Is.Empty);
ResetReceivers(testObjects);
}
[UnityTest]
#if UNITY_2022_1 || UNITY_2022_2
[Ignore("This test is currently not working in batch mode on Unity 2022.1 or 2022.2. Further investigation is needed.")]
#endif
public IEnumerator UIActionBindingsForMouselikeInput()
{
var testObjects = SetupUIScene(true);
testObjects.uiInputModule.enableBuiltinActionsAsFallback = false;
#pragma warning disable CS0618 // Type or member is obsolete -- For backwards compatibility with existing projects
testObjects.uiInputModule.activeInputMode = XRUIInputModule.ActiveInputMode.InputSystemActions;
#pragma warning restore CS0618
// Setup gamepad input with new input system
var asset = ScriptableObject.CreateInstance<InputActionAsset>();
// Create actions.
var map = new InputActionMap("UI Map");
asset.AddActionMap(map);
var pointAction = map.AddAction("Point", type: InputActionType.Value);
var leftClickAction = map.AddAction("Click", type: InputActionType.Button);
var middleClickAction = map.AddAction("MiddleClick", type: InputActionType.Button);
var rightClickAction = map.AddAction("RightClick", type: InputActionType.Button);
var scrollAction = map.AddAction("ScrollWheel", type: InputActionType.Value);
// Create bindings.
pointAction.AddBinding("<Mouse>/position");
leftClickAction.AddBinding("<Mouse>/leftButton");
middleClickAction.AddBinding("<Mouse>/middleButton");
rightClickAction.AddBinding("<Mouse>/rightButton");
scrollAction.AddBinding("<Mouse>/scroll");
// Associate to UIInputModule
testObjects.uiInputModule.pointAction = InputActionReference.Create(pointAction);
testObjects.uiInputModule.leftClickAction = InputActionReference.Create(leftClickAction);
testObjects.uiInputModule.middleClickAction = InputActionReference.Create(middleClickAction);
testObjects.uiInputModule.rightClickAction = InputActionReference.Create(rightClickAction);
testObjects.uiInputModule.scrollWheelAction = InputActionReference.Create(scrollAction);
map.Enable();
yield return MouseInputUIChecks(testObjects);
}
[UnityTest]
#if UNITY_2022_1 || UNITY_2022_2
[Ignore("This test is currently not working in batch mode on Unity 2022.1 or 2022.2. Further investigation is needed.")]
#endif
public IEnumerator UIActionBindingsForMouselikeInputUsingFallback()
{
var testObjects = SetupUIScene(true);
testObjects.uiInputModule.enableBuiltinActionsAsFallback = true;
testObjects.ResetInputActions();
yield return MouseInputUIChecks(testObjects);
}
IEnumerator MouseInputUIChecks(TestObjects testObjects)
{
// Enable device inputs
testObjects.uiInputModule.enableXRInput = false;
testObjects.uiInputModule.enableGamepadInput = false;
testObjects.uiInputModule.enableJoystickInput = false;
testObjects.uiInputModule.enableMouseInput = true;
testObjects.uiInputModule.enableTouchInput = false;
#pragma warning disable CS0618 // Type or member is obsolete -- For backwards compatibility with existing projects
testObjects.uiInputModule.activeInputMode = XRUIInputModule.ActiveInputMode.InputSystemActions;
#pragma warning restore CS0618
var leftUIReceiver = testObjects.leftUIReceiver;
var rightUIReceiver = testObjects.rightUIReceiver;
var globalUIReceiver = testObjects.globalUIReceiver;
var mouse = InputSystem.InputSystem.AddDevice<Mouse>();
mouse.MakeCurrent();
yield return null;
// Verify position changes and left click
Set(mouse.position, testObjects.From640x480ToScreen(10, 10));
yield return null;
Assert.That(testObjects.eventSystem.currentSelectedGameObject, Is.SameAs(leftUIReceiver.gameObject));
#if UNITY_2021_1_OR_NEWER
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(3));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Select));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Enter));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(8));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.UpdateSelected));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[6].type, Is.EqualTo(EventType.PointerMove));
Assert.That(globalUIReceiver.events[7].type, Is.EqualTo(EventType.UpdateSelected));
#else
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Select));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(5));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.UpdateSelected));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Enter));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.UpdateSelected));
#endif
ResetReceivers(testObjects);
// Verify left mouse button click received
Press(mouse.leftButton);
yield return null;
Release(mouse.leftButton);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Click));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<PointerEventData>());
var eventData = leftUIReceiver.events[1].data as PointerEventData;
Assert.That(eventData.button, Is.EqualTo(PointerEventData.InputButton.Left));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.UpdateSelected));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Click));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
// Verify middle mouse button click received
Press(mouse.middleButton);
yield return null;
Release(mouse.middleButton);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Click));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<PointerEventData>());
eventData = leftUIReceiver.events[1].data as PointerEventData;
Assert.That(eventData.button, Is.EqualTo(PointerEventData.InputButton.Middle));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.UpdateSelected));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Click));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
// Verify right mouse button click received
Press(mouse.rightButton);
yield return null;
Release(mouse.rightButton);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(4));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(leftUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(leftUIReceiver.events[2].type, Is.EqualTo(EventType.Up));
Assert.That(leftUIReceiver.events[3].type, Is.EqualTo(EventType.Click));
Assert.That(leftUIReceiver.events[0].data, Is.TypeOf<PointerEventData>());
eventData = leftUIReceiver.events[1].data as PointerEventData;
Assert.That(eventData.button, Is.EqualTo(PointerEventData.InputButton.Right));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(6));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Down));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.PotentialDrag));
Assert.That(globalUIReceiver.events[2].type, Is.EqualTo(EventType.UpdateSelected));
Assert.That(globalUIReceiver.events[3].type, Is.EqualTo(EventType.Up));
Assert.That(globalUIReceiver.events[4].type, Is.EqualTo(EventType.Click));
Assert.That(globalUIReceiver.events[5].type, Is.EqualTo(EventType.UpdateSelected));
ResetReceivers(testObjects);
// Verify scroll wheel input works
Set(mouse.scroll, Vector2.one, queueEventOnly: true);
yield return null;
Assert.That(leftUIReceiver.events, Has.Count.EqualTo(1));
Assert.That(leftUIReceiver.events[0].type, Is.EqualTo(EventType.Scroll));
Assert.That(globalUIReceiver.events, Has.Count.EqualTo(2));
Assert.That(globalUIReceiver.events[0].type, Is.EqualTo(EventType.Scroll));
Assert.That(globalUIReceiver.events[1].type, Is.EqualTo(EventType.UpdateSelected));
// Final cleanup
Object.Destroy(testObjects.eventSystem.gameObject);
}
[UnityTest]
public IEnumerator FindsNewDefaultCameraWhenCameraDisabled()
{
var camera1Go = new GameObject("Camera 1")
{
tag = "MainCamera",
};
var camera2Go = new GameObject("Camera 2")
{
tag = "MainCamera",
};
camera2Go.SetActive(false);
var camera1 = camera1Go.AddComponent<Camera>();
var camera2 = camera2Go.AddComponent<Camera>();
camera1.stereoTargetEye = StereoTargetEyeMask.None;
camera2.stereoTargetEye = StereoTargetEyeMask.None;
var eventSystemGo = new GameObject("EventSystem", typeof(TestEventSystem), typeof(XRUIInputModule));
var inputModule = eventSystemGo.GetComponent<XRUIInputModule>();
inputModule.enableXRInput = true;
inputModule.enableMouseInput = false;
inputModule.enableTouchInput = false;
inputModule.enableGamepadInput = false;
inputModule.enableJoystickInput = false;
var eventSystem = eventSystemGo.GetComponent<TestEventSystem>();
eventSystem.UpdateModules();
eventSystem.InvokeUpdate();
// Create the Ray Interactor to make sure the module actually processes XR input
var rayInteractor = TestUtilities.CreateRayInteractor();
rayInteractor.enableUIInteraction = true;
Assert.That(Camera.main, Is.SameAs(camera1));
yield return null;
Assert.That(Camera.main, Is.SameAs(camera1));
Assert.That(inputModule.uiCamera, Is.SameAs(camera1));
camera1Go.SetActive(false);
camera2Go.SetActive(true);
Assert.That(Camera.main, Is.SameAs(camera2));
// Need to change the model so the method to process XR input in the UIInputModule doesn't early return
rayInteractor.transform.localPosition = Vector3.one;
yield return null;
Assert.That(Camera.main, Is.SameAs(camera2));
Assert.That(inputModule.uiCamera, Is.SameAs(camera2));
}
[UnityTest]
public IEnumerator TrackedDeviceGraphicRaycasterClearsPokeDataWhenDisabled()
{
var testObjects = SetupUIScene(isWorldSpace: true);
var trackedDeviceGraphicRaycaster = testObjects.rightUIReceiver.GetComponentInParent<TrackedDeviceGraphicRaycaster>();
Assert.IsNotNull(trackedDeviceGraphicRaycaster);
var pokeInteractor = TestUtilities.CreatePokeInteractor();
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(pokeInteractor, out var model), Is.True);
// Ensure initial poke interactor is not poking
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
yield return null;
// Set poke interactor position to begin interaction with the canvas
var trackedDeviceGraphicRaycasterPosition = trackedDeviceGraphicRaycaster.transform.position;
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, pokeInteractor.pokeDepth / 2);
yield return null;
// Check that poke interactor is interacting with the UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
// Disable UI GameObject
trackedDeviceGraphicRaycaster.gameObject.SetActive(false);
yield return null;
// Check that poke interactor is no longre interacting with the disable UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
}
[UnityTest]
public IEnumerator TrackedDeviceGraphicRaycasterClearsPokeDataWhenDestroyed()
{
var testObjects = SetupUIScene(isWorldSpace: true);
var trackedDeviceGraphicRaycaster = testObjects.rightUIReceiver.GetComponentInParent<TrackedDeviceGraphicRaycaster>();
Assert.IsNotNull(trackedDeviceGraphicRaycaster);
var pokeInteractor = TestUtilities.CreatePokeInteractor();
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(pokeInteractor, out var model), Is.True);
// Ensure initial poke interactor is not poking
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
yield return null;
// Set poke interactor position to begin interaction with the canvas
var trackedDeviceGraphicRaycasterPosition = trackedDeviceGraphicRaycaster.transform.position;
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, pokeInteractor.pokeDepth / 2);
yield return null;
// Check that poke interactor is interacting with the UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
// Disable UI GameObject
Object.Destroy(trackedDeviceGraphicRaycaster.gameObject);
yield return null;
// Check that poke interactor is no longre interacting with the disable UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
}
[UnityTest]
public IEnumerator TrackedDeviceGraphicRaycasterPokeSelectVelocityTracked()
{
var testObjects = SetupUIScene(isWorldSpace: true);
var trackedDeviceGraphicRaycaster = testObjects.rightUIReceiver.GetComponentInParent<TrackedDeviceGraphicRaycaster>();
Assert.IsNotNull(trackedDeviceGraphicRaycaster);
var pokeInteractor = TestUtilities.CreatePokeInteractor();
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(pokeInteractor, out var trackedDeviceModel), Is.True);
yield return null;
// Set poke interactor position to begin interaction with the canvas
var trackedDeviceGraphicRaycasterPosition = trackedDeviceGraphicRaycaster.transform.position;
// Move to just outside poke Depth for first frame
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, pokeInteractor.pokeDepth + 0.01f);
yield return null;
// Ensure initial poke interactor is not poking yet
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
// Move inside poke depth limit
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, pokeInteractor.pokeDepth - 0.01f);
yield return null;
// Check that poke interactor is interacting with the UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.False);
// To trigger the Velocity calculation code in Poke via AttachPointVelocityTracker.GetAttachPointVelocity(),
// a minimum frame count has to be hit defined by AttachPointVelocityTracker.k_VelocityUpdateInterval (currently 6)
const int k_VelocityFrameCount = 5;
var pokeIntervalSize = pokeInteractor.pokeDepth / k_VelocityFrameCount;
for (int i = k_VelocityFrameCount; i > 0; --i)
{
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, pokeIntervalSize * i);
yield return null;
}
// Move poke interactor to within selection distance (depth percentage (2.5%) minus an extra 0.5% to meet tolerance).
var requiredPokeDepth = pokeInteractor.pokeDepth * (k_DepthPercentActivationThreshold - 0.005f);
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, requiredPokeDepth);
yield return null;
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.True);
// Move just outside of poke depth range to ensure interacting (hovering), but not selecting
requiredPokeDepth = pokeInteractor.pokeDepth * (k_DepthPercentActivationThreshold + 0.005f);
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, requiredPokeDepth);
yield return null;
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.False);
// Set poke interactor position to just outside hover range
pokeInteractor.transform.position = trackedDeviceGraphicRaycasterPosition - new Vector3(0, 0, pokeInteractor.pokeDepth + 0.1f);
yield return null;
// Check that poke interactor is no longre interacting with the disable UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
}
[UnityTest]
public IEnumerator TrackedDeviceGraphicRaycasterPokeDropdown()
{
var testObjects = SetupUIScene(isWorldSpace: true);
var trackedDeviceGraphicRaycaster = testObjects.rightUIReceiver.GetComponentInParent<TrackedDeviceGraphicRaycaster>();
Assert.IsNotNull(trackedDeviceGraphicRaycaster);
var dropDownGameObject = DefaultControls.CreateDropdown(new DefaultControls.Resources());
dropDownGameObject.transform.SetParent(testObjects.canvasGameObject.transform, false);
var dropdownComponent = dropDownGameObject.GetComponent<Dropdown>();
testObjects.canvasGameObject.transform.localScale = new Vector3(0.001f, 0.001f, 0.001f);
var pokeInteractor = TestUtilities.CreatePokeInteractor();
pokeInteractor.pokeDepth = 0.05f;
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(pokeInteractor, out var trackedDeviceModel), Is.True);
yield return null;
// Set poke interactor position to begin interaction with the canvas
var dropdownPosition = dropDownGameObject.transform.position;
var initialPokeDepth = pokeInteractor.pokeDepth + 0.01f;
// Move to just outside poke Depth for first frame
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, initialPokeDepth);
yield return null;
// Ensure initial poke interactor is not poking yet
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
// Move inside poke depth limit
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, pokeInteractor.pokeDepth - 0.01f);
yield return null;
// Check that poke interactor is interacting with the UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.False);
// To trigger the Velocity calculation code in Poke via AttachPointVelocityTracker.GetAttachPointVelocity(),
// a minimum frame count has to be hit defined by AttachPointVelocityTracker.k_VelocityUpdateInterval (currently 6)
const int k_VelocityFrameCount = 5;
var pokeIntervalSize = pokeInteractor.pokeDepth / k_VelocityFrameCount;
var requiredPokeDepth = pokeInteractor.pokeDepth * (k_DepthPercentActivationThreshold - 0.015f);
// Enter into poke
for (int i = k_VelocityFrameCount; i > 0; --i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, pokeIntervalSize * i);
yield return null;
}
pokeInteractor.transform.position = dropdownPosition + new Vector3(0, 0, requiredPokeDepth);
yield return null;
// extra frame for UI to process ray hit and selection update
yield return null;
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.True);
// Check event system thinks it is also selected correctly
Assert.AreEqual(dropDownGameObject, EventSystem.current.currentSelectedGameObject);
// Exit Poke
for (int i = 0; i < k_VelocityFrameCount; ++i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, pokeIntervalSize * i);
yield return null;
}
// Set poke interactor position to just outside hover range
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, initialPokeDepth);
yield return null;
// Check that poke interactor is no longre interacting with the disable UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
Assert.AreEqual(0, dropdownComponent.value);
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeInteractor.pokeDepth - 0.01f);
yield return null;
// Enter into poke
for (int i = k_VelocityFrameCount; i > 0; --i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeIntervalSize * i);
yield return null;
}
pokeInteractor.transform.position = dropdownPosition + new Vector3(0, -k_DropdownItemHeight, requiredPokeDepth);
yield return null;
yield return null;
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.True);
// Exit Poke
for (int i = 0; i < k_VelocityFrameCount; ++i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeIntervalSize * i);
yield return null;
}
// Set poke interactor position to just outside hover range
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeInteractor.pokeDepth + 0.01f);
yield return null;
Assert.AreEqual(1, dropdownComponent.value);
}
[UnityTest]
public IEnumerator TrackedDeviceGraphicRaycasterPokeDropdownOnSubcanvas()
{
var testObjects = SetupRig();
testObjects.canvasGameObject = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(TrackedDeviceGraphicRaycaster));
var canvas = testObjects.canvasGameObject.GetComponent<Canvas>();
canvas.worldCamera = testObjects.camera;
canvas.renderMode = RenderMode.WorldSpace;
canvas.transform.Translate(0.0f, 0.0f, 200.0f);
testObjects.canvasGameObject.AddComponent<Image>();
// Create subcanvas for UI tests
var subCanvasGo = new GameObject("Sub canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(TrackedDeviceGraphicRaycaster), typeof(Image));
var subCanvas = subCanvasGo.GetComponent<Canvas>();
var subCanvasTransform = subCanvas.transform;
var dropDownGameObject = DefaultControls.CreateDropdown(new DefaultControls.Resources());
dropDownGameObject.transform.SetParent(subCanvas.transform, false);
var dropdownComponent = dropDownGameObject.GetComponent<Dropdown>();
dropDownGameObject.transform.SetParent(subCanvasTransform, false);
subCanvasTransform.SetParent(testObjects.canvasGameObject.transform, false);
testObjects.canvasGameObject.transform.localScale = new Vector3(0.001f, 0.001f, 0.001f);
var pokeInteractor = TestUtilities.CreatePokeInteractor();
pokeInteractor.pokeDepth = 0.05f;
Assert.That(testObjects.uiInputModule.GetTrackedDeviceModel(pokeInteractor, out var trackedDeviceModel), Is.True);
yield return null;
// Set poke interactor position to begin interaction with the canvas
var dropdownPosition = dropDownGameObject.transform.position;
var initialPokeDepth = pokeInteractor.pokeDepth + 0.01f;
// Move to just outside poke Depth for first frame
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, initialPokeDepth);
yield return null;
// Ensure initial poke interactor is not poking yet
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
// Move inside poke depth limit
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, pokeInteractor.pokeDepth - 0.01f);
yield return null;
// Check that poke interactor is interacting with the UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.False);
// To trigger the Velocity calculation code in Poke via AttachPointVelocityTracker.GetAttachPointVelocity(),
// a minimum frame count has to be hit defined by AttachPointVelocityTracker.k_VelocityUpdateInterval (currently 6)
const int k_VelocityFrameCount = 5;
var pokeIntervalSize = pokeInteractor.pokeDepth / k_VelocityFrameCount;
var requiredPokeDepth = pokeInteractor.pokeDepth * (k_DepthPercentActivationThreshold - 0.015f);
// Enter into poke
for (int i = k_VelocityFrameCount; i > 0; --i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, pokeIntervalSize * i);
yield return null;
}
pokeInteractor.transform.position = dropdownPosition + new Vector3(0, 0, requiredPokeDepth);
yield return null;
// extra frame for UI to process ray hit and selection update
yield return null;
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.True);
// Check event system thinks it is also selected correctly
Assert.AreEqual(dropDownGameObject, EventSystem.current.currentSelectedGameObject);
// Exit Poke
for (int i = 0; i < k_VelocityFrameCount; ++i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, pokeIntervalSize * i);
yield return null;
}
// Set poke interactor position to just outside hover range
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, 0, initialPokeDepth);
yield return null;
// Check that poke interactor is no longre interacting with the disable UI
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.False);
Assert.AreEqual(0, dropdownComponent.value);
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeInteractor.pokeDepth - 0.01f);
yield return null;
// Enter into poke
for (int i = k_VelocityFrameCount; i > 0; --i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeIntervalSize * i);
yield return null;
}
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, -requiredPokeDepth);
yield return null;
yield return null;
Assert.That(TrackedDeviceGraphicRaycaster.IsPokeInteractingWithUI(pokeInteractor), Is.True);
Assert.IsTrue(pokeInteractor.TryGetUIModel(out trackedDeviceModel));
Assert.IsNotNull(trackedDeviceModel.selectableObject);
Assert.That(trackedDeviceModel.select, Is.True);
// Exit Poke
for (int i = 0; i < k_VelocityFrameCount; ++i)
{
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeIntervalSize * i);
yield return null;
}
// Set poke interactor position to just outside hover range
pokeInteractor.transform.position = dropdownPosition - new Vector3(0, k_DropdownItemHeight, pokeInteractor.pokeDepth + 0.01f);
yield return null;
Assert.AreEqual(1, dropdownComponent.value);
}
[TearDown]
public override void TearDown()
{
TestUtilities.DestroyAllSceneObjects();
base.TearDown();
}
}
}