PanelEventHandler.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. using UnityEngine.EventSystems;
  2. namespace UnityEngine.UIElements
  3. {
  4. #if PACKAGE_UITOOLKIT
  5. /// <summary>
  6. /// Use this class to handle input and send events to UI Toolkit runtime panels.
  7. /// </summary>
  8. [AddComponentMenu("UI Toolkit/Panel Event Handler (UI Toolkit)")]
  9. public class PanelEventHandler : UIBehaviour, IPointerMoveHandler, IPointerUpHandler, IPointerDownHandler,
  10. ISubmitHandler, ICancelHandler, IMoveHandler, IScrollHandler, ISelectHandler, IDeselectHandler,
  11. IPointerExitHandler, IPointerEnterHandler, IRuntimePanelComponent
  12. {
  13. private BaseRuntimePanel m_Panel;
  14. /// <summary>
  15. /// The panel that this component relates to. If panel is null, this component will have no effect.
  16. /// Will be set to null automatically if panel is Disposed from an external source.
  17. /// </summary>
  18. public IPanel panel
  19. {
  20. get => m_Panel;
  21. set
  22. {
  23. var newPanel = (BaseRuntimePanel)value;
  24. if (m_Panel != newPanel)
  25. {
  26. UnregisterCallbacks();
  27. m_Panel = newPanel;
  28. RegisterCallbacks();
  29. }
  30. }
  31. }
  32. private GameObject selectableGameObject => m_Panel?.selectableGameObject;
  33. private EventSystem eventSystem => UIElementsRuntimeUtility.activeEventSystem as EventSystem;
  34. private readonly PointerEvent m_PointerEvent = new PointerEvent();
  35. protected override void OnEnable()
  36. {
  37. base.OnEnable();
  38. RegisterCallbacks();
  39. }
  40. protected override void OnDisable()
  41. {
  42. base.OnDisable();
  43. UnregisterCallbacks();
  44. }
  45. void RegisterCallbacks()
  46. {
  47. if (m_Panel != null)
  48. {
  49. m_Panel.destroyed += OnPanelDestroyed;
  50. m_Panel.visualTree.RegisterCallback<FocusEvent>(OnElementFocus, TrickleDown.TrickleDown);
  51. m_Panel.visualTree.RegisterCallback<BlurEvent>(OnElementBlur, TrickleDown.TrickleDown);
  52. }
  53. }
  54. void UnregisterCallbacks()
  55. {
  56. if (m_Panel != null)
  57. {
  58. m_Panel.destroyed -= OnPanelDestroyed;
  59. m_Panel.visualTree.UnregisterCallback<FocusEvent>(OnElementFocus, TrickleDown.TrickleDown);
  60. m_Panel.visualTree.UnregisterCallback<BlurEvent>(OnElementBlur, TrickleDown.TrickleDown);
  61. }
  62. }
  63. void OnPanelDestroyed()
  64. {
  65. panel = null;
  66. }
  67. void OnElementFocus(FocusEvent e)
  68. {
  69. if (!m_Selecting && eventSystem != null)
  70. eventSystem.SetSelectedGameObject(selectableGameObject);
  71. }
  72. void OnElementBlur(BlurEvent e)
  73. {
  74. // Important: if panel discards focus entirely, it doesn't discard EventSystem selection necessarily.
  75. // Also note that if we arrive here through eventSystem.SetSelectedGameObject -> OnDeselect,
  76. // eventSystem.currentSelectedGameObject will still have its old value and we can't reaffect it immediately.
  77. }
  78. private bool m_Selecting;
  79. public void OnSelect(BaseEventData eventData)
  80. {
  81. m_Selecting = true;
  82. try
  83. {
  84. // This shouldn't conflict with EditorWindow calling Panel.Focus (only on Editor-type panels).
  85. m_Panel?.Focus();
  86. }
  87. finally
  88. {
  89. m_Selecting = false;
  90. }
  91. }
  92. public void OnDeselect(BaseEventData eventData)
  93. {
  94. m_Panel?.Blur();
  95. }
  96. public void OnPointerMove(PointerEventData eventData)
  97. {
  98. if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
  99. return;
  100. using (var e = PointerMoveEvent.GetPooled(m_PointerEvent))
  101. {
  102. SendEvent(e, eventData);
  103. }
  104. }
  105. public void OnPointerUp(PointerEventData eventData)
  106. {
  107. if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Up))
  108. return;
  109. using (var e = PointerUpEvent.GetPooled(m_PointerEvent))
  110. {
  111. SendEvent(e, eventData);
  112. if (e.pressedButtons == 0)
  113. PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, null);
  114. }
  115. }
  116. public void OnPointerDown(PointerEventData eventData)
  117. {
  118. if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Down))
  119. return;
  120. if (eventSystem != null)
  121. eventSystem.SetSelectedGameObject(selectableGameObject);
  122. using (var e = PointerDownEvent.GetPooled(m_PointerEvent))
  123. {
  124. SendEvent(e, eventData);
  125. PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, m_Panel);
  126. }
  127. }
  128. public void OnPointerExit(PointerEventData eventData)
  129. {
  130. if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
  131. return;
  132. // If a pointer exit is called while the pointer is still on top of this object, it means
  133. // there's something else removing the pointer, so we might need to send a PointerCancelEvent.
  134. // This is necessary for touch pointers that are being released, because in UGUI the object
  135. // that was last hovered will not always be the one receiving the pointer up.
  136. if (eventData.pointerCurrentRaycast.gameObject == gameObject &&
  137. eventData.pointerPressRaycast.gameObject != gameObject &&
  138. m_PointerEvent.pointerId != PointerId.mousePointerId)
  139. {
  140. using (var e = PointerCancelEvent.GetPooled(m_PointerEvent))
  141. {
  142. SendEvent(e, eventData);
  143. }
  144. }
  145. m_Panel.PointerLeavesPanel(m_PointerEvent.pointerId, m_PointerEvent.position);
  146. }
  147. public void OnPointerEnter(PointerEventData eventData)
  148. {
  149. if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
  150. return;
  151. m_Panel.PointerEntersPanel(m_PointerEvent.pointerId, m_PointerEvent.position);
  152. }
  153. public void OnSubmit(BaseEventData eventData)
  154. {
  155. if (m_Panel == null)
  156. return;
  157. using (var e = NavigationSubmitEvent.GetPooled())
  158. {
  159. SendEvent(e, eventData);
  160. }
  161. }
  162. public void OnCancel(BaseEventData eventData)
  163. {
  164. if (m_Panel == null)
  165. return;
  166. using (var e = NavigationCancelEvent.GetPooled())
  167. {
  168. SendEvent(e, eventData);
  169. }
  170. }
  171. public void OnMove(AxisEventData eventData)
  172. {
  173. if (m_Panel == null)
  174. return;
  175. using (var e = NavigationMoveEvent.GetPooled(eventData.moveVector))
  176. {
  177. SendEvent(e, eventData);
  178. }
  179. // TODO: if runtime panel has no internal navigation, switch to the next UGUI selectable element.
  180. }
  181. public void OnScroll(PointerEventData eventData)
  182. {
  183. if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
  184. return;
  185. var scrollDelta = eventData.scrollDelta;
  186. scrollDelta.y = -scrollDelta.y;
  187. const float kPixelPerLine = 20;
  188. // The old input system reported scroll deltas in lines, we report pixels.
  189. // Need to scale as the UI system expects lines.
  190. scrollDelta /= kPixelPerLine;
  191. using (var e = WheelEvent.GetPooled(scrollDelta, m_PointerEvent))
  192. {
  193. SendEvent(e, eventData);
  194. }
  195. }
  196. private void SendEvent(EventBase e, BaseEventData sourceEventData)
  197. {
  198. //e.runtimeEventData = sourceEventData;
  199. m_Panel.SendEvent(e);
  200. if (e.isPropagationStopped)
  201. sourceEventData.Use();
  202. }
  203. private void SendEvent(EventBase e, Event sourceEvent)
  204. {
  205. m_Panel.SendEvent(e);
  206. if (e.isPropagationStopped)
  207. sourceEvent.Use();
  208. }
  209. void Update()
  210. {
  211. if (m_Panel != null && eventSystem != null && eventSystem.currentSelectedGameObject == selectableGameObject)
  212. ProcessImguiEvents(true);
  213. }
  214. void LateUpdate()
  215. {
  216. // Empty the Event queue, look for EventModifiers.
  217. ProcessImguiEvents(false);
  218. }
  219. private Event m_Event = new Event();
  220. private static EventModifiers s_Modifiers = EventModifiers.None;
  221. void ProcessImguiEvents(bool isSelected)
  222. {
  223. bool first = true;
  224. while (Event.PopEvent(m_Event))
  225. {
  226. if (m_Event.type == EventType.Ignore || m_Event.type == EventType.Repaint ||
  227. m_Event.type == EventType.Layout)
  228. continue;
  229. s_Modifiers = first ? m_Event.modifiers : (s_Modifiers | m_Event.modifiers);
  230. first = false;
  231. if (isSelected)
  232. {
  233. ProcessKeyboardEvent(m_Event);
  234. if (m_Event.type != EventType.Used)
  235. ProcessTabEvent(m_Event);
  236. }
  237. }
  238. }
  239. void ProcessKeyboardEvent(Event e)
  240. {
  241. if (e.type == EventType.KeyUp)
  242. {
  243. if (e.character == '\0')
  244. {
  245. SendKeyUpEvent(e, e.keyCode, e.modifiers);
  246. }
  247. }
  248. else if (e.type == EventType.KeyDown)
  249. {
  250. if (e.character == '\0')
  251. {
  252. SendKeyDownEvent(e, e.keyCode, e.modifiers);
  253. }
  254. else
  255. {
  256. SendTextEvent(e, e.character, e.modifiers);
  257. }
  258. }
  259. }
  260. // TODO: add an ITabHandler interface
  261. void ProcessTabEvent(Event e)
  262. {
  263. if (e.type == EventType.KeyDown && e.character == '\t')
  264. {
  265. SendTabEvent(e, e.shift ? -1 : 1);
  266. }
  267. }
  268. private void SendTabEvent(Event e, int direction)
  269. {
  270. using (var ev = NavigationTabEvent.GetPooled(direction))
  271. {
  272. SendEvent(ev, e);
  273. }
  274. }
  275. private void SendKeyUpEvent(Event e, KeyCode keyCode, EventModifiers modifiers)
  276. {
  277. using (var ev = KeyUpEvent.GetPooled('\0', keyCode, modifiers))
  278. {
  279. SendEvent(ev, e);
  280. }
  281. }
  282. private void SendKeyDownEvent(Event e, KeyCode keyCode, EventModifiers modifiers)
  283. {
  284. using (var ev = KeyDownEvent.GetPooled('\0', keyCode, modifiers))
  285. {
  286. SendEvent(ev, e);
  287. }
  288. }
  289. private void SendTextEvent(Event e, char c, EventModifiers modifiers)
  290. {
  291. using (var ev = KeyDownEvent.GetPooled(c, KeyCode.None, modifiers))
  292. {
  293. SendEvent(ev, e);
  294. }
  295. }
  296. private bool ReadPointerData(PointerEvent pe, PointerEventData eventData, PointerEventType eventType = PointerEventType.Default)
  297. {
  298. if (eventSystem == null || eventSystem.currentInputModule == null)
  299. return false;
  300. pe.Read(this, eventData, eventType);
  301. // PointerEvents making it this far have been validated by PanelRaycaster already
  302. m_Panel.ScreenToPanel(pe.position, pe.deltaPosition,
  303. out var panelPosition, out var panelDelta, allowOutside:true);
  304. pe.SetPosition(panelPosition, panelDelta);
  305. return true;
  306. }
  307. enum PointerEventType
  308. {
  309. Default, Down, Up
  310. }
  311. class PointerEvent : IPointerEvent
  312. {
  313. public int pointerId { get; private set; }
  314. public string pointerType { get; private set; }
  315. public bool isPrimary { get; private set; }
  316. public int button { get; private set; }
  317. public int pressedButtons { get; private set; }
  318. public Vector3 position { get; private set; }
  319. public Vector3 localPosition { get; private set; }
  320. public Vector3 deltaPosition { get; private set; }
  321. public float deltaTime { get; private set; }
  322. public int clickCount { get; private set; }
  323. public float pressure { get; private set; }
  324. public float tangentialPressure { get; private set; }
  325. public float altitudeAngle { get; private set; }
  326. public float azimuthAngle { get; private set; }
  327. public float twist { get; private set; }
  328. public Vector2 radius { get; private set; }
  329. public Vector2 radiusVariance { get; private set; }
  330. public EventModifiers modifiers { get; private set; }
  331. public bool shiftKey => (modifiers & EventModifiers.Shift) != 0;
  332. public bool ctrlKey => (modifiers & EventModifiers.Control) != 0;
  333. public bool commandKey => (modifiers & EventModifiers.Command) != 0;
  334. public bool altKey => (modifiers & EventModifiers.Alt) != 0;
  335. public bool actionKey =>
  336. Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer
  337. ? commandKey
  338. : ctrlKey;
  339. public void Read(PanelEventHandler self, PointerEventData eventData, PointerEventType eventType)
  340. {
  341. pointerId = self.eventSystem.currentInputModule.ConvertUIToolkitPointerId(eventData);
  342. bool InRange(int i, int start, int count) => i >= start && i < start + count;
  343. pointerType =
  344. InRange(pointerId, PointerId.touchPointerIdBase, PointerId.touchPointerCount) ? PointerType.touch :
  345. InRange(pointerId, PointerId.penPointerIdBase, PointerId.penPointerCount) ? PointerType.pen :
  346. PointerType.mouse;
  347. isPrimary = pointerId == PointerId.mousePointerId ||
  348. pointerId == PointerId.touchPointerIdBase ||
  349. pointerId == PointerId.penPointerIdBase;
  350. button = (int)eventData.button;
  351. clickCount = eventData.clickCount;
  352. // Flip Y axis between input and UITK
  353. var h = Screen.height;
  354. var eventPosition = Display.RelativeMouseAt(eventData.position);
  355. if (eventPosition != Vector3.zero)
  356. {
  357. // We support multiple display and display identification based on event position.
  358. int eventDisplayIndex = (int)eventPosition.z;
  359. if (eventDisplayIndex > 0 && eventDisplayIndex < Display.displays.Length)
  360. h = Display.displays[eventDisplayIndex].systemHeight;
  361. }
  362. else
  363. {
  364. eventPosition = eventData.position;
  365. }
  366. var delta = eventData.delta;
  367. eventPosition.y = h - eventPosition.y;
  368. delta.y = -delta.y;
  369. localPosition = position = eventPosition;
  370. deltaPosition = delta;
  371. deltaTime = 0; //TODO: find out what's expected here. Time since last frame? Since last sent event?
  372. pressure = eventData.pressure;
  373. tangentialPressure = eventData.tangentialPressure;
  374. altitudeAngle = eventData.altitudeAngle;
  375. azimuthAngle = eventData.azimuthAngle;
  376. twist = eventData.twist;
  377. radius = eventData.radius;
  378. radiusVariance = eventData.radiusVariance;
  379. modifiers = s_Modifiers;
  380. if (eventType == PointerEventType.Default)
  381. {
  382. button = -1;
  383. clickCount = 0;
  384. }
  385. else
  386. {
  387. button = button >= 0 ? button : 0;
  388. clickCount = Mathf.Max(1, clickCount);
  389. if (eventType == PointerEventType.Down)
  390. PointerDeviceState.PressButton(pointerId, button);
  391. else if (eventType == PointerEventType.Up)
  392. PointerDeviceState.ReleaseButton(pointerId, button);
  393. }
  394. pressedButtons = PointerDeviceState.GetPressedButtons(pointerId);
  395. }
  396. public void SetPosition(Vector3 positionOverride, Vector3 deltaOverride)
  397. {
  398. localPosition = position = positionOverride;
  399. deltaPosition = deltaOverride;
  400. }
  401. }
  402. }
  403. #endif
  404. }