using UnityEngine.EventSystems; namespace UnityEngine.UIElements { #if PACKAGE_UITOOLKIT /// /// Use this class to handle input and send events to UI Toolkit runtime panels. /// [AddComponentMenu("UI Toolkit/Panel Event Handler (UI Toolkit)")] public class PanelEventHandler : UIBehaviour, IPointerMoveHandler, IPointerUpHandler, IPointerDownHandler, ISubmitHandler, ICancelHandler, IMoveHandler, IScrollHandler, ISelectHandler, IDeselectHandler, IRuntimePanelComponent { private BaseRuntimePanel m_Panel; /// /// 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. /// 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 readonly PointerEvent m_PointerEvent = new PointerEvent(); 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(OnElementFocus, TrickleDown.TrickleDown); m_Panel.visualTree.RegisterCallback(OnElementBlur, TrickleDown.TrickleDown); } } void UnregisterCallbacks() { if (m_Panel != null) { m_Panel.destroyed -= OnPanelDestroyed; m_Panel.visualTree.UnregisterCallback(OnElementFocus, TrickleDown.TrickleDown); m_Panel.visualTree.UnregisterCallback(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, true)) return; using (var e = PointerMoveEvent.GetPooled(m_PointerEvent)) { SendEvent(e, eventData); } } public void OnPointerUp(PointerEventData eventData) { if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, false)) return; using (var e = PointerUpEvent.GetPooled(m_PointerEvent)) { SendEvent(e, eventData); } } public void OnPointerDown(PointerEventData eventData) { if (m_Panel == null || !ReadPointerData(m_PointerEvent, eventData, false)) return; if (eventSystem != null) eventSystem.SetSelectedGameObject(selectableGameObject); using (var e = PointerDownEvent.GetPooled(m_PointerEvent)) { SendEvent(e, eventData); } } public void OnSubmit(BaseEventData eventData) { if (m_Panel == null) return; using (var e = NavigationSubmitEvent.GetPooled()) { SendEvent(e, eventData); } } public void OnCancel(BaseEventData eventData) { if (m_Panel == null) return; using (var e = NavigationCancelEvent.GetPooled()) { SendEvent(e, eventData); } } public void OnMove(AxisEventData eventData) { if (m_Panel == null) return; using (var e = NavigationMoveEvent.GetPooled(eventData.moveVector)) { 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, true)) 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.position)) { 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); if (e.isPropagationStopped) sourceEvent.Use(); } void Update() { if (m_Panel != null && eventSystem != null && eventSystem.currentSelectedGameObject == selectableGameObject) ProcessImguiEvents(true); } void LateUpdate() { // Empty the Event queue, look for EventModifiers. ProcessImguiEvents(false); } private Event m_Event = new Event(); private static EventModifiers s_Modifiers = EventModifiers.None; void ProcessImguiEvents(bool isSelected) { 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 (isSelected) { ProcessKeyboardEvent(m_Event); if (m_Event.type != EventType.Used) ProcessTabEvent(m_Event); } } } void ProcessKeyboardEvent(Event e) { if (e.type == EventType.KeyUp) { if (e.character == '\0') { SendKeyUpEvent(e, e.keyCode, e.modifiers); } } else if (e.type == EventType.KeyDown) { if (e.character == '\0') { SendKeyDownEvent(e, e.keyCode, e.modifiers); } else { SendTextEvent(e, e.character, e.modifiers); } } } // TODO: add an ITabHandler interface void ProcessTabEvent(Event e) { if (e.type == EventType.KeyDown && e.character == '\t') { SendTabEvent(e, e.shift ? -1 : 1); } } private void SendTabEvent(Event e, int direction) { using (var ev = NavigationTabEvent.GetPooled(direction)) { SendEvent(ev, e); } } private void SendKeyUpEvent(Event e, KeyCode keyCode, EventModifiers modifiers) { using (var ev = KeyUpEvent.GetPooled('\0', keyCode, modifiers)) { SendEvent(ev, e); } } private void SendKeyDownEvent(Event e, KeyCode keyCode, EventModifiers modifiers) { using (var ev = KeyDownEvent.GetPooled('\0', keyCode, modifiers)) { SendEvent(ev, e); } } private void SendTextEvent(Event e, char c, EventModifiers modifiers) { using (var ev = KeyDownEvent.GetPooled(c, KeyCode.None, modifiers)) { SendEvent(ev, e); } } private bool ReadPointerData(PointerEvent pe, PointerEventData eventData, bool isMove) { if (eventSystem == null || eventSystem.currentInputModule == null) return false; pe.Read(this, eventData, isMove); if (!m_Panel.ScreenToPanel(pe.position, pe.deltaPosition, out var panelPosition, out var panelDelta)) return false; pe.SetPosition(panelPosition, panelDelta); return true; } 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 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, bool isMove) { 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; button = (int)eventData.button; pressedButtons = PointerDeviceState.GetPressedButtons(pointerId); clickCount = eventData.clickCount; // Flip Y axis between input and UITK var h = Screen.height; var eventPosition = Display.RelativeMouseAt(eventData.position); if (eventPosition != Vector3.zero) { // We support multiple display and display identification based on event position. int eventDisplayIndex = (int)eventPosition.z; if (eventDisplayIndex > 0 && eventDisplayIndex < Display.displays.Length) h = Display.displays[eventDisplayIndex].systemHeight; } else { eventPosition = eventData.position; } 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; radius = eventData.radius; radiusVariance = eventData.radiusVariance; modifiers = s_Modifiers; if (isMove) { button = -1; clickCount = 0; } else { button = button >= 0 ? button : 0; clickCount = Mathf.Max(1, clickCount); } } public void SetPosition(Vector3 positionOverride, Vector3 deltaOverride) { localPosition = position = positionOverride; deltaPosition = deltaOverride; } } } #endif }