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