暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PanelEventHandler.cs 19KB

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