DefaultPointerInputDetector.cs 11 KB

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