DefaultPointerInputDetector.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // Copyright (c) 2022 Vuplex Inc. All rights reserved.
  2. //
  3. // Licensed under the Vuplex Commercial Software Library License, you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // https://vuplex.com/commercial-library-license
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. using System;
  15. using System.Reflection;
  16. using UnityEngine;
  17. using UnityEngine.EventSystems;
  18. using UnityEngine.UI;
  19. using Vuplex.WebView.Internal;
  20. #if VUPLEX_MRTK
  21. using Microsoft.MixedReality.Toolkit.Input;
  22. #endif
  23. namespace Vuplex.WebView {
  24. [HelpURL("https://developer.vuplex.com/webview/IPointerInputDetector")]
  25. public class DefaultPointerInputDetector : MonoBehaviour,
  26. IPointerInputDetector,
  27. IBeginDragHandler,
  28. IDragHandler,
  29. IPointerClickHandler,
  30. IPointerDownHandler,
  31. IPointerEnterHandler,
  32. IPointerExitHandler,
  33. IPointerUpHandler,
  34. #if VUPLEX_MRTK
  35. IMixedRealityPointerHandler,
  36. #endif
  37. IScrollHandler {
  38. public event EventHandler<EventArgs<Vector2>> BeganDrag;
  39. public event EventHandler<EventArgs<Vector2>> Dragged;
  40. public event EventHandler<PointerEventArgs> PointerDown;
  41. public event EventHandler PointerExited;
  42. public event EventHandler<EventArgs<Vector2>> PointerMoved;
  43. public event EventHandler<PointerEventArgs> PointerUp;
  44. public event EventHandler<ScrolledEventArgs> Scrolled;
  45. public bool PointerMovedEnabled { get; set; }
  46. /// <see cref="IBeginDragHandler"/>
  47. public void OnBeginDrag(PointerEventData eventData) {
  48. _raiseBeganDragEvent(_convertToEventArgs(eventData));
  49. }
  50. /// <see cref="IDragHandler"/>
  51. public void OnDrag(PointerEventData eventData) {
  52. // The point is Vector3.zero when the user drags off of the screen.
  53. if (!_positionIsZero(eventData)) {
  54. _raiseDraggedEvent(_convertToEventArgs(eventData));
  55. }
  56. }
  57. /// <summary>
  58. /// VRIF requires IPointerClickHandler to be implemented in order to detect the object
  59. /// and invoke its OnPointerDown() and OnPointerUp() methods.
  60. /// </summary>
  61. /// <see cref="IPointerClickHandler"/>
  62. public void OnPointerClick(PointerEventData eventData) {}
  63. /// <see cref="IPointerDownHandler"/>
  64. public virtual void OnPointerDown(PointerEventData eventData) {
  65. _raisePointerDownEvent(_convertToPointerEventArgs(eventData));
  66. }
  67. /// <see cref="IPointerEnterHandler"/>
  68. public void OnPointerEnter(PointerEventData eventData) {
  69. _isHovering = true;
  70. }
  71. /// <see cref="IPointerExitHandler"/>
  72. public void OnPointerExit(PointerEventData eventData) {
  73. _isHovering = false;
  74. _raisePointerExitedEvent(EventArgs.Empty);
  75. }
  76. /// <see cref="IPointerUpHandler"/>
  77. public virtual void OnPointerUp(PointerEventData eventData) {
  78. _raisePointerUpEvent(_convertToPointerEventArgs(eventData));
  79. }
  80. /// <see cref="IScrollHandler"/>
  81. public void OnScroll(PointerEventData eventData) {
  82. var scrollDelta = new Vector2(
  83. -eventData.scrollDelta.x,
  84. -eventData.scrollDelta.y
  85. );
  86. _raiseScrolledEvent(new ScrolledEventArgs(scrollDelta, _convertToNormalizedPoint(eventData)));
  87. }
  88. bool _isHovering;
  89. EventArgs<Vector2> _convertToEventArgs(Vector3 worldPosition) {
  90. var screenPoint = _convertToNormalizedPoint(worldPosition);
  91. return new EventArgs<Vector2>(screenPoint);
  92. }
  93. EventArgs<Vector2> _convertToEventArgs(PointerEventData pointerEventData) {
  94. var screenPoint = _convertToNormalizedPoint(pointerEventData);
  95. return new EventArgs<Vector2>(screenPoint);
  96. }
  97. protected virtual Vector2 _convertToNormalizedPoint(PointerEventData pointerEventData) {
  98. return _convertToNormalizedPoint(pointerEventData.pointerCurrentRaycast.worldPosition);
  99. }
  100. protected virtual Vector2 _convertToNormalizedPoint(Vector3 worldPosition) {
  101. // Note: transform.parent is WebViewPrefabResizer
  102. var localPosition = transform.parent.InverseTransformPoint(worldPosition);
  103. return new Vector2(1 - localPosition.x, -1 * localPosition.y);
  104. }
  105. PointerEventArgs _convertToPointerEventArgs(PointerEventData eventData) {
  106. return new PointerEventArgs {
  107. Point = _convertToNormalizedPoint(eventData),
  108. Button = (MouseButton)eventData.button,
  109. // StandaloneInputModule incorrectly specifies a click count of 0
  110. // for PointerDown events, so set the minimum to 1 click.
  111. ClickCount = Math.Max(eventData.clickCount, 1)
  112. };
  113. }
  114. /// <summary>
  115. /// Unity's event system doesn't include a standard pointer event
  116. /// for hovering (i.e. there's no `IPointerHoverHandler` interface).
  117. /// So, this method implements the equivalent functionality by
  118. /// using the protected `PointerInputModule.GetLastPointerEventData()`
  119. /// method to detect where the pointer is hovering.
  120. /// </summary>
  121. PointerEventData _getLastPointerEventData() {
  122. var pointerInputModule = EventSystem.current?.currentInputModule as PointerInputModule;
  123. if (pointerInputModule == null) {
  124. return null;
  125. }
  126. // Use reflection to get access to the protected `GetPointerData()`
  127. // method. Unity isn't going to change this API because most input modules
  128. // extend PointerInputModule. Note that `GetPointerData()` is used instead
  129. // of `GetLastPointerEventData()` because the latter doesn't work with
  130. // the Oculus SDK's OVRInputModule.
  131. var args = new object[] { PointerInputModule.kMouseLeftId, null, false };
  132. pointerInputModule.GetType().InvokeMember(
  133. "GetPointerData",
  134. BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic,
  135. null,
  136. pointerInputModule,
  137. args
  138. );
  139. // The second argument is an out param.
  140. var pointerEventData = args[1] as PointerEventData;
  141. return pointerEventData;
  142. }
  143. protected virtual bool _positionIsZero(PointerEventData eventData) {
  144. return eventData.pointerCurrentRaycast.worldPosition == Vector3.zero;
  145. }
  146. protected void _raiseBeganDragEvent(EventArgs<Vector2> eventArgs) {
  147. BeganDrag?.Invoke(this, eventArgs);
  148. }
  149. protected void _raiseDraggedEvent(EventArgs<Vector2> eventArgs) {
  150. Dragged?.Invoke(this, eventArgs);
  151. }
  152. protected void _raisePointerDownEvent(PointerEventArgs eventArgs) {
  153. PointerDown?.Invoke(this, eventArgs);
  154. }
  155. protected void _raisePointerExitedEvent(EventArgs eventArgs) {
  156. PointerExited?.Invoke(this, eventArgs);
  157. }
  158. void _raisePointerMovedIfNeeded() {
  159. if (!(PointerMovedEnabled && _isHovering)) {
  160. return;
  161. }
  162. var pointerEventData = _getLastPointerEventData();
  163. if (pointerEventData == null) {
  164. return;
  165. }
  166. var screenPoint = _convertToNormalizedPoint(pointerEventData);
  167. if (!(screenPoint.x >= 0f && screenPoint.y >= 0f)) {
  168. // This can happen while the prefab is being resized.
  169. return;
  170. }
  171. _raisePointerMovedEvent(new EventArgs<Vector2>(screenPoint));
  172. }
  173. protected void _raisePointerMovedEvent(EventArgs<Vector2> eventArgs) {
  174. PointerMoved?.Invoke(this, eventArgs);
  175. }
  176. protected void _raisePointerUpEvent(PointerEventArgs eventArgs) {
  177. PointerUp?.Invoke(this, eventArgs);
  178. }
  179. protected void _raiseScrolledEvent(ScrolledEventArgs eventArgs) {
  180. Scrolled?.Invoke(this, eventArgs);
  181. }
  182. void Update() => _raisePointerMovedIfNeeded();
  183. // Code specific to Microsoft's Mixed Reality Toolkit.
  184. #if VUPLEX_MRTK
  185. bool _beganDragEmitted;
  186. /// <see cref="IMixedRealityPointerHandler"/>
  187. public void OnPointerClicked(MixedRealityPointerEventData eventData) {}
  188. /// <see cref="IMixedRealityPointerHandler"/>
  189. public void OnPointerDragged(MixedRealityPointerEventData eventData) {
  190. var eventArgs = _convertToEventArgs(eventData.Pointer.Result.Details.Point);
  191. if (_beganDragEmitted) {
  192. _raiseDraggedEvent(eventArgs);
  193. } else {
  194. _beganDragEmitted = true;
  195. _raiseBeganDragEvent(eventArgs);
  196. }
  197. }
  198. /// <see cref="IMixedRealityPointerHandler"/>
  199. public void OnPointerDown(MixedRealityPointerEventData eventData) {
  200. // Set IsTargetPositionLockedOnFocusLock to false, or else the Point
  201. // coordinates will be locked and won't change in OnPointerDragged or OnPointerUp.
  202. eventData.Pointer.IsTargetPositionLockedOnFocusLock = false;
  203. _beganDragEmitted = false;
  204. var screenPoint = _convertToNormalizedPoint(eventData.Pointer.Result.Details.Point);
  205. _raisePointerDownEvent(new PointerEventArgs { Point = screenPoint });
  206. }
  207. /// <see cref="IMixedRealityPointerHandler"/>
  208. public void OnPointerUp(MixedRealityPointerEventData eventData) {
  209. var screenPoint = _convertToNormalizedPoint(eventData.Pointer.Result.Details.Point);
  210. _raisePointerUpEvent(new PointerEventArgs { Point = screenPoint });
  211. }
  212. void Start() {
  213. WebViewLogger.Log("Just a heads-up: please ignore the warning 'BoxCollider is null...' warning from MRTK. WebViewPrefab doesn't use a BoxCollider, so it sets the bounds of NearInteractionTouchable manually, but MRTK doesn't provide a way to disable the warning.");
  214. // Add a NearInteractionTouchable script to allow touch interactions
  215. // to trigger the IMixedRealityPointerHandler methods.
  216. var touchable = gameObject.AddComponent<NearInteractionTouchable>();
  217. touchable.EventsToReceive = TouchableEventType.Pointer;
  218. touchable.SetBounds(Vector2.one);
  219. touchable.SetLocalForward(new Vector3(0, 0, -1));
  220. }
  221. #endif
  222. }
  223. }