123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- using UnityEngine.EventSystems;
- using UnityEngine.UI;
-
- namespace UnityEngine.UIElements
- {
- // This code is disabled unless the UI Toolkit package or the com.unity.modules.uielements module are present.
- // The UIElements module is always present in the Editor but it can be stripped from a project build if unused.
- #if PACKAGE_UITOOLKIT
- /// <summary>
- /// Use this class to handle input and send events to UI Toolkit runtime panels.
- /// </summary>
- [AddComponentMenu("UI Toolkit/Panel Event Handler (UI Toolkit)")]
- public class PanelEventHandler : UIBehaviour, IPointerMoveHandler, IPointerUpHandler, IPointerDownHandler,
- ISubmitHandler, ICancelHandler, IMoveHandler, IScrollHandler, ISelectHandler, IDeselectHandler,
- IPointerExitHandler, IPointerEnterHandler, IRuntimePanelComponent, IPointerClickHandler
- {
- private BaseRuntimePanel m_Panel;
-
- /// <summary>
- /// The panel that this component relates to. If panel is null, this component will have no effect.
- /// Will be set to null automatically if panel is Disposed from an external source.
- /// </summary>
- public IPanel panel
- {
- get => m_Panel;
- set
- {
- var newPanel = (BaseRuntimePanel)value;
- if (m_Panel != newPanel)
- {
- UnregisterCallbacks();
- m_Panel = newPanel;
- RegisterCallbacks();
- }
- }
- }
-
- private GameObject selectableGameObject => m_Panel?.selectableGameObject;
- private EventSystem eventSystem => UIElementsRuntimeUtility.activeEventSystem as EventSystem;
-
- private bool isCurrentFocusedPanel => m_Panel != null && eventSystem != null &&
- eventSystem.currentSelectedGameObject == selectableGameObject;
-
- private Focusable currentFocusedElement => m_Panel?.focusController.GetLeafFocusedElement();
-
- private readonly PointerEvent m_PointerEvent = new PointerEvent();
-
- private float m_LastClickTime = 0;
-
- protected override void OnEnable()
- {
- base.OnEnable();
- RegisterCallbacks();
- }
-
- protected override void OnDisable()
- {
- base.OnDisable();
- UnregisterCallbacks();
- }
-
- void RegisterCallbacks()
- {
- if (m_Panel != null)
- {
- m_Panel.destroyed += OnPanelDestroyed;
- m_Panel.visualTree.RegisterCallback<FocusEvent>(OnElementFocus, TrickleDown.TrickleDown);
- m_Panel.visualTree.RegisterCallback<BlurEvent>(OnElementBlur, TrickleDown.TrickleDown);
- }
- }
-
- void UnregisterCallbacks()
- {
- if (m_Panel != null)
- {
- m_Panel.destroyed -= OnPanelDestroyed;
- m_Panel.visualTree.UnregisterCallback<FocusEvent>(OnElementFocus, TrickleDown.TrickleDown);
- m_Panel.visualTree.UnregisterCallback<BlurEvent>(OnElementBlur, TrickleDown.TrickleDown);
- }
- }
-
- void OnPanelDestroyed()
- {
- panel = null;
- }
-
- void OnElementFocus(FocusEvent e)
- {
- if (!m_Selecting && eventSystem != null)
- eventSystem.SetSelectedGameObject(selectableGameObject);
- }
-
- void OnElementBlur(BlurEvent e)
- {
- // Important: if panel discards focus entirely, it doesn't discard EventSystem selection necessarily.
- // Also note that if we arrive here through eventSystem.SetSelectedGameObject -> OnDeselect,
- // eventSystem.currentSelectedGameObject will still have its old value and we can't reaffect it immediately.
- }
-
- private bool m_Selecting;
- public void OnSelect(BaseEventData eventData)
- {
- m_Selecting = true;
- try
- {
- // This shouldn't conflict with EditorWindow calling Panel.Focus (only on Editor-type panels).
- m_Panel?.Focus();
- }
- finally
- {
- m_Selecting = false;
- }
- }
-
- public void OnDeselect(BaseEventData eventData)
- {
- m_Panel?.Blur();
- }
-
- public void OnPointerMove(PointerEventData eventData)
- {
- if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
- return;
-
- using (var e = PointerMoveEvent.GetPooled(m_PointerEvent))
- {
- SendEvent(e, eventData);
- }
- }
-
- public void OnPointerUp(PointerEventData eventData)
- {
- if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Up))
- return;
-
- using (var e = PointerUpEvent.GetPooled(m_PointerEvent))
- {
- SendEvent(e, eventData);
-
- if (e.pressedButtons == 0)
- PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, null);
- }
- }
-
- public void OnPointerDown(PointerEventData eventData)
- {
- if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, PointerEventType.Down))
- return;
-
- if (eventSystem != null)
- eventSystem.SetSelectedGameObject(selectableGameObject);
-
- using (var e = PointerDownEvent.GetPooled(m_PointerEvent))
- {
- SendEvent(e, eventData);
-
- PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, m_Panel);
- }
- }
-
- public void OnPointerExit(PointerEventData eventData)
- {
- if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
- return;
-
- // If a pointer exit is called while the pointer is still on top of this object, it means
- // there's something else removing the pointer, so we might need to send a PointerCancelEvent.
- // This is necessary for touch pointers that are being released, because in UGUI the object
- // that was last hovered will not always be the one receiving the pointer up.
- if (eventData.pointerCurrentRaycast.gameObject == gameObject &&
- eventData.pointerPressRaycast.gameObject != gameObject &&
- m_PointerEvent.pointerId != PointerId.mousePointerId)
- {
- using (var e = PointerCancelEvent.GetPooled(m_PointerEvent))
- {
- SendEvent(e, eventData);
-
- if (e.pressedButtons == 0)
- PointerDeviceState.SetPlayerPanelWithSoftPointerCapture(e.pointerId, null);
- }
- }
-
- m_Panel.PointerLeavesPanel(m_PointerEvent.pointerId, m_PointerEvent.position);
- }
-
- public void OnPointerEnter(PointerEventData eventData)
- {
- if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
- return;
-
- m_Panel.PointerEntersPanel(m_PointerEvent.pointerId, m_PointerEvent.position);
- }
-
- public void OnPointerClick(PointerEventData eventData)
- {
- m_LastClickTime = Time.unscaledTime;
- }
-
- public void OnSubmit(BaseEventData eventData)
- {
- if (m_Panel == null)
- return;
-
- // Allow KeyDown/KeyUp events to be processed before navigation events.
- var target = currentFocusedElement ?? m_Panel.visualTree;
- ProcessImguiEvents(target);
-
- using (var e = NavigationSubmitEvent.GetPooled(s_Modifiers))
- {
- e.target = target;
- SendEvent(e, eventData);
- }
- }
-
- public void OnCancel(BaseEventData eventData)
- {
- if (m_Panel == null)
- return;
-
- // Allow KeyDown/KeyUp events to be processed before navigation events.
- var target = currentFocusedElement ?? m_Panel.visualTree;
- ProcessImguiEvents(target);
-
- using (var e = NavigationCancelEvent.GetPooled(s_Modifiers))
- {
- e.target = target;
- SendEvent(e, eventData);
- }
- }
-
- public void OnMove(AxisEventData eventData)
- {
- if (m_Panel == null)
- return;
-
- // Allow KeyDown/KeyUp events to be processed before navigation events.
- var target = currentFocusedElement ?? m_Panel.visualTree;
- ProcessImguiEvents(target);
-
- using (var e = NavigationMoveEvent.GetPooled(eventData.moveVector, s_Modifiers))
- {
- e.target = target;
- SendEvent(e, eventData);
- }
-
- // TODO: if runtime panel has no internal navigation, switch to the next UGUI selectable element.
- }
-
- public void OnScroll(PointerEventData eventData)
- {
- if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData))
- return;
-
- var scrollDelta = eventData.scrollDelta;
- scrollDelta.y = -scrollDelta.y;
-
- const float kPixelPerLine = 20;
- // The old input system reported scroll deltas in lines, we report pixels.
- // Need to scale as the UI system expects lines.
- scrollDelta /= kPixelPerLine;
-
- using (var e = WheelEvent.GetPooled(scrollDelta, m_PointerEvent))
- {
- SendEvent(e, eventData);
- }
- }
-
- private void SendEvent(EventBase e, BaseEventData sourceEventData)
- {
- //e.runtimeEventData = sourceEventData;
- m_Panel.SendEvent(e);
- if (e.isPropagationStopped)
- sourceEventData.Use();
- }
-
- private void SendEvent(EventBase e, Event sourceEvent)
- {
- m_Panel.SendEvent(e);
-
- // Don't call sourceEvent.Use() because DefaultEventSystem doesn't call it either
- // and we want to have the same behavior as much as possible.
- // See UGUIEventSystemTests.KeyDownStoppedDoesntPreventNavigationEvents for a test requires this.
- }
-
- internal void Update()
- {
- if (isCurrentFocusedPanel)
- ProcessImguiEvents(currentFocusedElement ?? m_Panel.visualTree);
- }
-
- void LateUpdate()
- {
- // Empty the Event queue, look for EventModifiers.
- ProcessImguiEvents(null);
- }
-
- private Event m_Event = new Event();
- private static EventModifiers s_Modifiers = EventModifiers.None;
-
- // Send IMGUI events to given focus-based target, if any, or simply flush the event queue if not.
- // For uniformity of composite events (keyDown vs navigation), target should remain the same
- // throughout the entire processing cycle.
- void ProcessImguiEvents(Focusable target)
- {
- bool first = true;
-
- while (Event.PopEvent(m_Event))
- {
- if (m_Event.type == EventType.Ignore || m_Event.type == EventType.Repaint ||
- m_Event.type == EventType.Layout)
- continue;
-
- s_Modifiers = first ? m_Event.modifiers : (s_Modifiers | m_Event.modifiers);
- first = false;
-
- if (target != null)
- {
- ProcessKeyboardEvent(m_Event, target);
- if (eventSystem.sendNavigationEvents)
- ProcessTabEvent(m_Event, target);
- }
- }
- }
-
- void ProcessKeyboardEvent(Event e, Focusable target)
- {
- if (e.type == EventType.KeyUp)
- {
- SendKeyUpEvent(e, target);
- }
- else if (e.type == EventType.KeyDown)
- {
- SendKeyDownEvent(e, target);
- }
- }
-
- // TODO: add an ITabHandler interface
- void ProcessTabEvent(Event e, Focusable target)
- {
- if (e.ShouldSendNavigationMoveEventRuntime())
- {
- SendTabEvent(e, e.shift ? NavigationMoveEvent.Direction.Previous : NavigationMoveEvent.Direction.Next, target);
- }
- }
-
- private void SendTabEvent(Event e, NavigationMoveEvent.Direction direction, Focusable target)
- {
- using (var ev = NavigationMoveEvent.GetPooled(direction, s_Modifiers))
- {
- ev.target = target;
- SendEvent(ev, e);
- }
- }
-
- private void SendKeyUpEvent(Event e, Focusable target)
- {
- // Use UIElementsRuntimeUtility.CreateEvent because DefaultEventSystem uses it too
- // and we want to have the same behavior as much as possible.
- using (var ev = (KeyUpEvent) UIElementsRuntimeUtility.CreateEvent(e))
- {
- ev.target = target;
- SendEvent(ev, e);
- }
- }
-
- private void SendKeyDownEvent(Event e, Focusable target)
- {
- // Use UIElementsRuntimeUtility.CreateEvent because DefaultEventSystem uses it too
- // and we want to have the same behavior as much as possible.
- using (var ev = (KeyDownEvent) UIElementsRuntimeUtility.CreateEvent(e))
- {
- ev.target = target;
- SendEvent(ev, e);
- }
- }
-
- private bool ReadPointerData(PointerEvent pe, PointerEventData eventData, PointerEventType eventType = PointerEventType.Default)
- {
- if (eventSystem == null || eventSystem.currentInputModule == null)
- return false;
-
- pe.Read(this, eventData, eventType);
-
- // PointerEvents making it this far have been validated by PanelRaycaster already
- m_Panel.ScreenToPanel(pe.position, pe.deltaPosition,
- out var panelPosition, out var panelDelta, allowOutside:true);
-
- pe.SetPosition(panelPosition, panelDelta);
- return true;
- }
-
- enum PointerEventType
- {
- Default, Down, Up
- }
-
- class PointerEvent : IPointerEvent
- {
- public int pointerId { get; private set; }
- public string pointerType { get; private set; }
- public bool isPrimary { get; private set; }
- public int button { get; private set; }
- public int pressedButtons { get; private set; }
- public Vector3 position { get; private set; }
- public Vector3 localPosition { get; private set; }
- public Vector3 deltaPosition { get; private set; }
- public float deltaTime { get; private set; }
- public int clickCount { get; private set; }
- public float pressure { get; private set; }
- public float tangentialPressure { get; private set; }
- public float altitudeAngle { get; private set; }
- public float azimuthAngle { get; private set; }
- public float twist { get; private set; }
- public Vector2 tilt { get; private set; }
- public PenStatus penStatus { get; private set; }
- public Vector2 radius { get; private set; }
- public Vector2 radiusVariance { get; private set; }
- public EventModifiers modifiers { get; private set; }
-
- public bool shiftKey => (modifiers & EventModifiers.Shift) != 0;
- public bool ctrlKey => (modifiers & EventModifiers.Control) != 0;
- public bool commandKey => (modifiers & EventModifiers.Command) != 0;
- public bool altKey => (modifiers & EventModifiers.Alt) != 0;
-
- public bool actionKey =>
- Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer
- ? commandKey
- : ctrlKey;
-
- public void Read(PanelEventHandler self, PointerEventData eventData, PointerEventType eventType)
- {
- pointerId = self.eventSystem.currentInputModule.ConvertUIToolkitPointerId(eventData);
-
- bool InRange(int i, int start, int count) => i >= start && i < start + count;
-
- pointerType =
- InRange(pointerId, PointerId.touchPointerIdBase, PointerId.touchPointerCount) ? PointerType.touch :
- InRange(pointerId, PointerId.penPointerIdBase, PointerId.penPointerCount) ? PointerType.pen :
- PointerType.mouse;
-
- isPrimary = pointerId == PointerId.mousePointerId ||
- pointerId == PointerId.touchPointerIdBase ||
- pointerId == PointerId.penPointerIdBase;
-
- // Flip Y axis between input and UITK
- var h = Screen.height;
-
- Vector3 eventPosition = MultipleDisplayUtilities.GetRelativeMousePositionForRaycast(eventData);
- int eventDisplayIndex = (int)eventPosition.z;
-
- if (eventDisplayIndex > 0 && eventDisplayIndex < Display.displays.Length)
- h = Display.displays[eventDisplayIndex].systemHeight;
-
- var delta = eventData.delta;
- eventPosition.y = h - eventPosition.y;
- delta.y = -delta.y;
-
- localPosition = position = eventPosition;
- deltaPosition = delta;
-
- deltaTime = 0; //TODO: find out what's expected here. Time since last frame? Since last sent event?
- pressure = eventData.pressure;
- tangentialPressure = eventData.tangentialPressure;
- altitudeAngle = eventData.altitudeAngle;
- azimuthAngle = eventData.azimuthAngle;
- twist = eventData.twist;
- tilt = eventData.tilt;
- penStatus = eventData.penStatus;
- radius = eventData.radius;
- radiusVariance = eventData.radiusVariance;
-
- modifiers = s_Modifiers;
-
- if (eventType == PointerEventType.Default)
- {
- button = -1;
- clickCount = 0;
- }
- else
- {
- button = Mathf.Max(0, (int)eventData.button);
- clickCount = eventData.clickCount;
-
- if (eventType == PointerEventType.Down)
- {
- // UUM-57082: InputSystem doesn't reset clickCount on delay until after it sends PointerDown
- // This is not perfect but it's the best we can do with incomplete information.
- if (Time.unscaledTime > self.m_LastClickTime + ClickDetector.s_DoubleClickTime * 0.001f)
- clickCount = 0;
-
- // Case 1379054: UIToolkit assumes clickCount is increased before PointerDown, but UGUI does it after.
- clickCount++;
-
- PointerDeviceState.PressButton(pointerId, button);
- }
- else if (eventType == PointerEventType.Up)
- {
- PointerDeviceState.ReleaseButton(pointerId, button);
- }
-
- clickCount = Mathf.Max(1, clickCount);
- }
-
- pressedButtons = PointerDeviceState.GetPressedButtons(pointerId);
- }
-
- public void SetPosition(Vector3 positionOverride, Vector3 deltaOverride)
- {
- localPosition = position = positionOverride;
- deltaPosition = deltaOverride;
- }
- }
- }
- #endif
- }
|