using System; using System.Collections.Generic; using UnityEngine.EventSystems; namespace UnityEngine.UIElements { // This code is disabled unless the com.unity.modules.uielements module is activated in the package manager. #if PACKAGE_UITOOLKIT /// /// Enables UI Toolkit interoperability with uGUI events. /// internal class UIToolkitInteroperabilityBridge { [Flags] public enum EventHandlerTypes { ScreenOverlay = 1, WorldSpace = 2, } private EventSystem m_EventSystem; internal EventSystem eventSystem { get => m_EventSystem; set { if (m_EventSystem == value) return; Debug.Assert(!m_Started && !m_Enabled, "Expect EventSystem set before OnEnable and Start"); m_EventSystem = value; } } private bool m_OverrideUIToolkitEvents = true; private EventHandlerTypes m_HandlerTypes = EventHandlerTypes.ScreenOverlay | EventHandlerTypes.WorldSpace; private LayerMask m_WorldPickingLayers = Physics.DefaultRaycastLayers; private float m_WorldPickingMaxDistance = Mathf.Infinity; private bool m_CreateDefaultPanelComponents = true; private bool m_Started; private bool m_Enabled; private bool m_IsTrackingPanels; private GameObject m_WorldSpaceGo; private bool m_PendingRegistration; // True if we need to retry registration when utility becomes available public bool overrideUIToolkitEvents { get => m_OverrideUIToolkitEvents; internal set { m_OverrideUIToolkitEvents = value; ApplyOverrideUIToolkitEvents(); } } public EventHandlerTypes handlerTypes { get => m_HandlerTypes; internal set { m_HandlerTypes = value; ApplyOtherProperties(); } } public int worldPickingLayers { get => m_WorldPickingLayers; internal set => m_WorldPickingLayers = value; } public float worldPickingMaxDistance { get => m_WorldPickingMaxDistance; internal set => m_WorldPickingMaxDistance = value; } public bool createDefaultPanelComponents { get => m_CreateDefaultPanelComponents; internal set { m_CreateDefaultPanelComponents = value; ApplyOtherProperties(); } } private bool shouldTrackPanels => overrideUIToolkitEvents && createDefaultPanelComponents && m_Started && m_Enabled; private void StartTrackingUIToolkitPanels() { if (m_IsTrackingPanels || !shouldTrackPanels) return; // Can be null if runtime panels have not been created yet; mark for retry in Update() if (IRuntimePanel.uIElementsRuntimeUtility == null) { m_PendingRegistration = true; return; } foreach (var panel in IRuntimePanel.uIElementsRuntimeUtility.GetSortedPlayerPanelsInternal()) { StartTrackingPanel(panel); } IRuntimePanel.uIElementsRuntimeUtility.AddOnCreatePanelAction( StartTrackingPanel); m_IsTrackingPanels = true; } // Use object storage to prevent generic type parameter from preserving BaseRuntimePanel during code stripping private readonly HashSet trackedPanels = new(); private void StartTrackingPanel(IRuntimePanel panel) { trackedPanels.Add(panel); } private void StopTrackingUIToolkitPanels() { if (!m_IsTrackingPanels) return; // Can be null if runtime panels have not been created or UI Toolkit is stripped IRuntimePanel.uIElementsRuntimeUtility?.RemoveOnCreatePanelAction( StartTrackingPanel); m_IsTrackingPanels = false; foreach (var panelObj in trackedPanels) { DestroyPanelGameObject(panelObj); } trackedPanels.Clear(); DestroyWorldSpacePanelGameObject(); } private void UpdatePanelGameObject(IRuntimePanel panel) { var handlerType = panel.isFlat ? EventHandlerTypes.ScreenOverlay : EventHandlerTypes.WorldSpace; if ((m_HandlerTypes & handlerType) != 0) { CreatePanelGameObject(panel); } else { DestroyPanelGameObject(panel); } } // Use interface storage to prevent generic type parameter from preserving BaseRuntimePanel during code stripping private readonly Dictionary destroyedActions = new(); private void CreatePanelGameObject(IRuntimePanel panel) { if (panel.selectableGameObject == null) { var go = new GameObject(panel.name); go.AddComponent(); go.AddComponent(); go.transform.SetParent(m_EventSystem.transform); panel.selectableGameObject = go; var destroyed = destroyedActions[panel] = () => DestroyPanelGameObject(panel); panel.destroyed += destroyed; } } private void DestroyPanelGameObject(IRuntimePanel panel) { var go = panel.selectableGameObject; if (go != null) { if (!destroyedActions.Remove(panel, out var destroyed)) return; // this object wasn't created by us, so leave it untouched panel.destroyed -= destroyed; panel.selectableGameObject = null; UIRUtility.Destroy(go); } } private void CreateWorldSpacePanelGameObject() { // This will destroy m_WorldSpaceGo if raycaster components need to change. ApplyCameraProperties(); if (m_WorldSpaceGo == null) { var go = new GameObject("WorldDocumentRaycaster"); go.transform.SetParent(m_EventSystem.transform); if (m_InputSettings.defaultEventCameraIsMainCamera) { go.AddComponent(); } else { foreach (var cam in m_InputSettings.eventCameras) { var raycaster = go.AddComponent(); if (raycaster != null) { raycaster.camera = cam; } } } m_WorldSpaceGo = go; } } private void DestroyWorldSpacePanelGameObject() { var go = m_WorldSpaceGo; m_WorldSpaceGo = null; UIRUtility.Destroy(go); } public void Start() { m_Started = true; StartTrackingUIToolkitPanels(); } public void OnEnable() { if (m_Enabled) return; m_Enabled = true; if (PanelInputState.current != null) Apply(PanelInputState.current); PanelInputState.onApply += Apply; if (m_Started) StartTrackingUIToolkitPanels(); // Can be null if runtime panels have not been created yet; mark for retry in Update() if (m_OverrideUIToolkitEvents) { if (IRuntimePanel.uIElementsRuntimeUtility != null) IRuntimePanel.uIElementsRuntimeUtility.RegisterEventSystem(m_EventSystem); else m_PendingRegistration = true; } } public void OnDisable() { if (!m_Enabled) return; m_Enabled = false; PanelInputState.onApply -= Apply; StopTrackingUIToolkitPanels(); // Can be null if runtime panels have not been created or UI Toolkit is stripped IRuntimePanel.uIElementsRuntimeUtility?.UnregisterEventSystem(m_EventSystem); m_PendingRegistration = false; } public void Update() { // Retry registration if it was pending and utility is now available if (m_PendingRegistration && IRuntimePanel.uIElementsRuntimeUtility != null) { m_PendingRegistration = false; // Retry event system registration if (m_OverrideUIToolkitEvents && m_Enabled) IRuntimePanel.uIElementsRuntimeUtility.RegisterEventSystem(m_EventSystem); // Retry panel tracking if (shouldTrackPanels) StartTrackingUIToolkitPanels(); } UpdatePanelGameObjects(); } private PanelInputSettings m_InputSettings = PanelInputSettings.Default; void Apply(IPanelInputProvider input) { m_InputSettings = input != null ? input.settings : PanelInputSettings.Default; m_OverrideUIToolkitEvents = m_InputSettings.shouldRedirectInput; m_HandlerTypes = EventHandlerTypes.ScreenOverlay | (m_InputSettings.processWorldSpaceInput ? EventHandlerTypes.WorldSpace : 0); m_WorldPickingLayers = m_InputSettings.interactionLayers; m_WorldPickingMaxDistance = m_InputSettings.maxInteractionDistance; m_CreateDefaultPanelComponents = m_InputSettings.autoCreatePanelComponents; ApplyOverrideUIToolkitEvents(); ApplyCameraProperties(); ApplyOtherProperties(); } private bool m_OldOverrideUIToolkitEvents = true; private EventHandlerTypes m_OldHandlerTypes = EventHandlerTypes.ScreenOverlay | EventHandlerTypes.WorldSpace; private bool m_OldCreateDefaultPanelComponents = true; private bool m_OldDefaultEventCameraIsMainCamera = true; private long m_OldEventCamerasHash = 0; private void ApplyOverrideUIToolkitEvents() { if (m_OldOverrideUIToolkitEvents == m_OverrideUIToolkitEvents) return; m_OldOverrideUIToolkitEvents = m_OverrideUIToolkitEvents; if (!m_Enabled) return; // Can be null if runtime panels have not been created yet; mark for retry in Update() if (m_OverrideUIToolkitEvents) { if (IRuntimePanel.uIElementsRuntimeUtility != null) IRuntimePanel.uIElementsRuntimeUtility.RegisterEventSystem(m_EventSystem); else m_PendingRegistration = true; } else { IRuntimePanel.uIElementsRuntimeUtility?.UnregisterEventSystem(m_EventSystem); m_PendingRegistration = false; } UpdatePanelTracking(); } private void ApplyCameraProperties() { bool dirty = false; if (m_OldDefaultEventCameraIsMainCamera != m_InputSettings.defaultEventCameraIsMainCamera) { m_OldDefaultEventCameraIsMainCamera = m_InputSettings.defaultEventCameraIsMainCamera; dirty = true; } if (!m_InputSettings.defaultEventCameraIsMainCamera) { var hashCode = 0; foreach (var cam in m_InputSettings.eventCameras) { hashCode = (hashCode * 397) ^ cam.GetHashCode(); } if (m_OldEventCamerasHash != hashCode) { m_OldEventCamerasHash = hashCode; dirty = true; } } else { m_OldEventCamerasHash = 0; } if (dirty) DestroyWorldSpacePanelGameObject(); } private void ApplyOtherProperties() { bool dirty = false; if (m_OldHandlerTypes != m_HandlerTypes) { m_OldHandlerTypes = m_HandlerTypes; dirty = true; } if (m_OldCreateDefaultPanelComponents != m_CreateDefaultPanelComponents) { m_OldCreateDefaultPanelComponents = m_CreateDefaultPanelComponents; dirty = true; } if (dirty) UpdatePanelTracking(); } private void UpdatePanelTracking() { if (shouldTrackPanels) { StartTrackingUIToolkitPanels(); } else { StopTrackingUIToolkitPanels(); } } // Use object storage to prevent generic type parameter from preserving BaseRuntimePanel during code stripping private List m_PanelsToRemove = new(); private void UpdatePanelGameObjects() { if (!m_IsTrackingPanels) return; bool isWorldSpaceActive = false; foreach (var panel in trackedPanels) { if (panel.disposed) { m_PanelsToRemove.Add(panel); continue; } UpdatePanelGameObject(panel); isWorldSpaceActive |= !panel.isFlat; } foreach (var panelObj in m_PanelsToRemove) { trackedPanels.Remove(panelObj); } m_PanelsToRemove.Clear(); if (isWorldSpaceActive && (m_HandlerTypes & EventHandlerTypes.WorldSpace) != 0) { CreateWorldSpacePanelGameObject(); } else { DestroyWorldSpacePanelGameObject(); } } } #endif }