Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions Assets/Tests/InputSystem/Plugins/UITests.InputModuleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#if UNITY_INPUT_SYSTEM_SENDPOINTERHOVERTOPARENT
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.UI;
using UnityEngine.TestTools;
using UnityEngine.UI;

internal partial class UITests
{
#pragma warning disable CS0649
public class InputModuleTests : CoreTestsFixture
{
private InputSystemUIInputModule m_InputModule;
private Mouse m_Mouse;
private Image m_Image;
private Image m_NestedImage;

private bool sendPointerHoverToParent
{
set => m_InputModule.sendPointerHoverToParent = value;
}

private Vector2 mousePosition
{
set { Set(m_Mouse.position, value); }
}

public override void Setup()
{
base.Setup();

var canvas = new GameObject("Canvas").AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.gameObject.AddComponent<GraphicRaycaster>();

m_Image = new GameObject("Image").AddComponent<Image>();
m_Image.gameObject.transform.SetParent(canvas.transform);
RectTransform imageRectTransform = m_Image.GetComponent<RectTransform>();
imageRectTransform.sizeDelta = new Vector2(400f, 400f);
imageRectTransform.localPosition = Vector3.zero;

m_NestedImage = new GameObject("NestedImage").AddComponent<Image>();
m_NestedImage.gameObject.transform.SetParent(m_Image.transform);
RectTransform nestedImageRectTransform = m_NestedImage.GetComponent<RectTransform>();
nestedImageRectTransform.sizeDelta = new Vector2(200f, 200f);
nestedImageRectTransform.localPosition = Vector3.zero;

GameObject go = new GameObject("Event System");
var eventSystem = go.AddComponent<EventSystem>();
eventSystem.pixelDragThreshold = 1;

m_InputModule = go.AddComponent<InputSystemUIInputModule>();
Cursor.lockState = CursorLockMode.None;

m_Mouse = InputSystem.AddDevice<Mouse>();
var actions = ScriptableObject.CreateInstance<InputActionAsset>();
var uiActions = actions.AddActionMap("UI");
var pointAction = uiActions.AddAction("point", type: InputActionType.PassThrough);
pointAction.AddBinding("<Mouse>/position");
pointAction.Enable();
m_InputModule.point = InputActionReference.Create(pointAction);
}

[UnityTest]
public IEnumerator PointerEnterChildShouldNotFullyExit_NotSendPointerEventToParent()
{
sendPointerHoverToParent = false;
PointerExitCallbackCheck callbackCheck = m_Image.gameObject.AddComponent<PointerExitCallbackCheck>();
m_NestedImage.gameObject.AddComponent<PointerExitCallbackCheck>();
var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2);

mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
mousePosition = screenMiddle;
yield return null;
Assert.IsTrue(callbackCheck.pointerData.fullyExited == false);
}

[UnityTest]
public IEnumerator PointerEnterChildShouldNotExit_SendPointerEventToParent()
{
sendPointerHoverToParent = true;
PointerExitCallbackCheck callbackCheck = m_Image.gameObject.AddComponent<PointerExitCallbackCheck>();
m_NestedImage.gameObject.AddComponent<PointerExitCallbackCheck>();
var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2);

mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
mousePosition = screenMiddle;
yield return null;
Assert.IsTrue(callbackCheck.pointerData == null);
}

[UnityTest]
public IEnumerator PointerEnterChildShouldNotReenter()
{
PointerEnterCallbackCheck callbackCheck =
m_NestedImage.gameObject.AddComponent<PointerEnterCallbackCheck>();
var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2);

mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
mousePosition = screenMiddle;
yield return null;
Assert.IsTrue(callbackCheck.pointerData.reentered == false);
}

[UnityTest]
public IEnumerator PointerExitChildShouldReenter_NotSendPointerEventToParent()
{
sendPointerHoverToParent = false;
PointerEnterCallbackCheck callbackCheck =
m_Image.gameObject.AddComponent<PointerEnterCallbackCheck>();
var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2);

mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
mousePosition = screenMiddle;
yield return null;
mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
Assert.IsTrue(callbackCheck.pointerData.reentered == true);
}

[UnityTest]
public IEnumerator PointerExitChildShouldNotSendEnter_SendPointerEventToParent()
{
sendPointerHoverToParent = true;
m_NestedImage.gameObject.AddComponent<PointerEnterCallbackCheck>();
var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2);

mousePosition = screenMiddle;
yield return null;
PointerEnterCallbackCheck callbackCheck =
m_Image.gameObject.AddComponent<PointerEnterCallbackCheck>();
mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
Assert.IsTrue(callbackCheck.pointerData == null);
}

[UnityTest]
public IEnumerator PointerExitChildShouldFullyExit()
{
PointerExitCallbackCheck callbackCheck =
m_NestedImage.gameObject.AddComponent<PointerExitCallbackCheck>();
var screenMiddle = new Vector2(Screen.width / 2, Screen.height / 2);

mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
mousePosition = screenMiddle;
yield return null;
mousePosition = screenMiddle - new Vector2(150, 150);
yield return null;
Assert.IsTrue(callbackCheck.pointerData.fullyExited == true);
}

public class PointerExitCallbackCheck : MonoBehaviour, IPointerExitHandler
{
public PointerEventData pointerData { get; private set; }

public void OnPointerExit(PointerEventData eventData)
{
pointerData = eventData;
}
}

public class PointerEnterCallbackCheck : MonoBehaviour, IPointerEnterHandler
{
public PointerEventData pointerData { get; private set; }

public void OnPointerEnter(PointerEventData eventData)
{
pointerData = eventData;
}
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Assets/Tests/InputSystem/Plugins/UITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
#pragma warning disable CS0649
////TODO: app focus handling

internal class UITests : CoreTestsFixture
internal partial class UITests : CoreTestsFixture
{
private struct TestObjects
{
Expand Down
5 changes: 5 additions & 0 deletions Assets/Tests/InputSystem/Unity.InputSystem.Tests.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
"name": "Unity",
"expression": "6000.0.11",
"define": "UNITY_INPUT_SYSTEM_INPUT_MODULE_SCROLL_DELTA"
},
{
"name": "Unity",
"expression": "6000.0.15",
"define": "UNITY_INPUT_SYSTEM_SENDPOINTERHOVERTOPARENT"
}
],
"noEngineReferences": false
Expand Down
1 change: 1 addition & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ however, it has to be formatted properly to pass verification tests.

### Fixed
- Fixed memory allocation on every frame when using UIDocument without EventSystem. [ISXB-953](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-953)
- Fixed InputSystemUIInputModule calling pointer events on parent objects even when the "Send Pointer Hover To Parent" is off. [ISXB-586](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-586)

### Changed
- Use `ProfilerMarker` instead of `Profiler.BeginSample` and `Profiler.EndSample` when appropriate to enable recording of profiling data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,32 +435,74 @@ private void ProcessPointerMovement(ExtendedPointerEventData eventData, GameObje
if (eventData.pointerEnter == currentPointerTarget && currentPointerTarget)
return;

var commonRoot = FindCommonRoot(eventData.pointerEnter, currentPointerTarget)?.transform;
Transform commonRoot = FindCommonRoot(eventData.pointerEnter, currentPointerTarget)?.transform;
Transform pointerParent = ((Component)currentPointerTarget.GetComponentInParent<IPointerExitHandler>())?.transform;

// We walk up the tree until a common root and the last entered and current entered object is found.
// Then send exit and enter events up to, but not including, the common root.
// ** or when !m_SendPointerEnterToParent, stop when meeting a gameobject with an exit event handler
if (eventData.pointerEnter != null)
{
for (var current = eventData.pointerEnter.transform; current != null && current != commonRoot; current = current.parent)
var current = eventData.pointerEnter.transform;
while (current != null)
{
// if we reach the common root break out!
if (sendPointerHoverToParent && current == commonRoot)
break;

// if we reach a PointerExitEvent break out!
if (!sendPointerHoverToParent && current == pointerParent)
break;

eventData.fullyExited = current != commonRoot && eventData.pointerEnter != currentPointerTarget;
ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerExitHandler);
eventData.hovered.Remove(current.gameObject);

if (sendPointerHoverToParent)
current = current.parent;

// if we reach the common root break out!
if (current == commonRoot)
break;

if (!sendPointerHoverToParent)
current = current.parent;
}
}

// now issue the enter call up to but not including the common root
Transform oldPointerEnter = eventData.pointerEnter?.transform;
eventData.pointerEnter = currentPointerTarget;
if (currentPointerTarget != null)
{
for (var current = currentPointerTarget.transform;
current != null && current != commonRoot && !PointerShouldIgnoreTransform(current);
current = current.parent)
Transform current = currentPointerTarget.transform;
while (current != null && !PointerShouldIgnoreTransform(current))
{
eventData.reentered = current == commonRoot && current != oldPointerEnter;
// if we are sending the event to parent, they are already in hover mode at that point. No need to bubble up the event.
if (sendPointerHoverToParent && eventData.reentered)
break;

ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerEnterHandler);
#if UNITY_2021_1_OR_NEWER
if (wasMoved)
ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerMoveHandler);
#endif
eventData.hovered.Add(current.gameObject);

// stop when encountering an object with the pointerEnterHandler
if (!sendPointerHoverToParent && current.GetComponent<IPointerEnterHandler>() != null)
break;

if (sendPointerHoverToParent)
current = current.parent;

// if we reach the common root break out!
if (current == commonRoot)
break;

if (!sendPointerHoverToParent)
current = current.parent;
}
}
}
Expand Down Expand Up @@ -2420,6 +2462,15 @@ private struct InputActionReferenceState

[NonSerialized] private GameObject m_LocalMultiPlayerRoot;

#if UNITY_INPUT_SYSTEM_SENDPOINTERHOVERTOPARENT
// Needed for testing.
internal new bool sendPointerHoverToParent
{
get => base.sendPointerHoverToParent;
set => base.sendPointerHoverToParent = value;
}
#endif

/// <summary>
/// Controls the origin point of raycasts when the cursor is locked.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
"name": "com.unity.profiling.core",
"expression": "1.0.2",
"define": "UNITY_INPUT_SYSTEM_USE_PROFILER_MARKERS"
},
{
"name": "Unity",
"expression": "6000.0.15",
"define": "UNITY_INPUT_SYSTEM_SENDPOINTERHOVERTOPARENT"
}
],
"noEngineReferences": false
Expand Down