编辑器UI快速选择扩展

编辑器UI快速选择扩展
https://gist.github.com/yasirkula/06edc780beaa4d8705b3564d60886fa6
在这里插入图片描述

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_2021_2_OR_NEWER
using PrefabStage = UnityEditor.SceneManagement.PrefabStage;
using PrefabStageUtility = UnityEditor.SceneManagement.PrefabStageUtility;
#elif UNITY_2018_3_OR_NEWER
using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage;
using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility;
#endif

public class SceneViewUIObjectPickerContextWindow : EditorWindow
{
	private struct Entry
	{
		public readonly RectTransform RectTransform;
		public readonly List<Entry> Children;

		public Entry( RectTransform rectTransform )
		{
			RectTransform = rectTransform;
			Children = new List<Entry>( 2 );
		}
	}

	private readonly List<RectTransform> uiObjects = new List<RectTransform>( 16 );
	private readonly List<string> uiObjectLabels = new List<string>( 16 );

	private static RectTransform hoveredUIObject;
	private static readonly Vector3[] hoveredUIObjectCorners = new Vector3[4];
	private static readonly List<ICanvasRaycastFilter> raycastFilters = new List<ICanvasRaycastFilter>( 4 );

	private static double lastRightClickTime;
	private static Vector2 lastRightPos;
	private static bool blockSceneViewInput;

	private static MethodInfo screenFittedRectGetter;
	private static FieldInfo editorWindowHostViewGetter;
	private static PropertyInfo hostViewContainerWindowGetter;

	private const float Padding = 1f;
	private float RowHeight { get { return EditorGUIUtility.singleLineHeight; } }
	private GUIStyle RowGUIStyle { get { return "MenuItem"; } }

	private void ShowContextWindow( List<Entry> results )
	{
		StringBuilder sb = new StringBuilder( 100 );
		InitializeUIObjectsRecursive( results, 0, sb );

		GUIStyle rowGUIStyle = RowGUIStyle;
		float preferredWidth = 0f;
		foreach( string label in uiObjectLabels )
			preferredWidth = Mathf.Max( preferredWidth, rowGUIStyle.CalcSize( new GUIContent( label ) ).x );

		ShowAsDropDown( new Rect(), new Vector2( preferredWidth + Padding * 2f, uiObjects.Count * RowHeight + Padding * 2f ) );

        Rect rect = new Rect(GUIUtility.GUIToScreenPoint(Event.current.mousePosition) - new Vector2(0f, position.height), position.size); // Show dropdown above the cursor instead of below the cursor
        position = GetScreenFittedRect(rect, this);
	}

	private void InitializeUIObjectsRecursive( List<Entry> results, int depth, StringBuilder sb )
	{
		foreach( Entry entry in results )
		{
			sb.Length = 0;

			uiObjects.Add( entry.RectTransform );
			uiObjectLabels.Add( sb.Append( ' ', depth * 4 ).Append( entry.RectTransform.name ).ToString() );

			if( entry.Children.Count > 0 )
				InitializeUIObjectsRecursive( entry.Children, depth + 1, sb );
		}
	}

	protected void OnEnable()
	{
		wantsMouseMove = wantsMouseEnterLeaveWindow = true;
		wantsLessLayoutEvents = false;
		blockSceneViewInput = true;
	}

	protected void OnDisable()
	{
		hoveredUIObject = null;
		SceneView.RepaintAll();
	}

	protected void OnGUI()
	{
		Event ev = Event.current;

		float rowWidth = position.width - Padding * 2f, rowHeight = RowHeight;
		GUIStyle rowGUIStyle = RowGUIStyle;
		int hoveredRowIndex = -1;
		for( int i = 0; i < uiObjects.Count; i++ )
		{
			Rect rect = new Rect( Padding, Padding + i * rowHeight, rowWidth, rowHeight );
			if( GUI.Button( rect, uiObjectLabels[i], rowGUIStyle ) )
			{
				if( uiObjects[i] != null )
					Selection.activeTransform = uiObjects[i];

				blockSceneViewInput = false;
				ev.Use();
				Close();
				GUIUtility.ExitGUI();
			}

			if( hoveredRowIndex < 0 && ev.type == EventType.MouseMove && rect.Contains( ev.mousePosition ) )
				hoveredRowIndex = i;
		}

		if( ev.type == EventType.MouseMove || ev.type == EventType.MouseLeaveWindow )
		{
			RectTransform hoveredUIObject = ( hoveredRowIndex >= 0 ) ? uiObjects[hoveredRowIndex] : null;
			if( hoveredUIObject != SceneViewUIObjectPickerContextWindow.hoveredUIObject )
			{
				SceneViewUIObjectPickerContextWindow.hoveredUIObject = hoveredUIObject;
				Repaint();
				SceneView.RepaintAll();
			}
		}
	}

	[InitializeOnLoadMethod]
	private static void OnSceneViewGUI()
	{
		SceneView.duringSceneGui += ( SceneView sceneView ) =>
		{
			/// Couldn't get <see cref="EventType.ContextClick"/> to work here in Unity 5.6 so implemented context click detection manually
			Event ev = Event.current;
			switch( ev.type )
			{
				case EventType.MouseDown:
				{
					if( ev.button == 1 )
					{
						lastRightClickTime = EditorApplication.timeSinceStartup;
						lastRightPos = ev.mousePosition;
					}
					else if( blockSceneViewInput )
					{
						// User has clicked outside the context window to close it. Ignore this click in Scene view if it's left click
						blockSceneViewInput = false;

						if( ev.button == 0 )
						{
							GUIUtility.hotControl = 0;
							ev.Use();
						}
					}

					break;
				}
				case EventType.MouseUp:
				{
					if( ev.button == 1 && EditorApplication.timeSinceStartup - lastRightClickTime < 0.2 && ( ev.mousePosition - lastRightPos ).magnitude < 2f )
						OnSceneViewRightClicked( sceneView );

					break;
				}
			}

			if( hoveredUIObject != null )
			{
				hoveredUIObject.GetWorldCorners( hoveredUIObjectCorners );
				Handles.DrawSolidRectangleWithOutline( hoveredUIObjectCorners, new Color( 1f, 1f, 0f, 0.25f ), Color.black );
			}
		};
	}

	private static void OnSceneViewRightClicked( SceneView sceneView )
	{
		// Find all UI objects under the cursor
		Vector2 pointerPos = HandleUtility.GUIPointToScreenPixelCoordinate( Event.current.mousePosition );
		Entry rootEntry = new Entry( null );
		PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
		if( prefabStage != null && prefabStage.stageHandle.IsValid() && prefabStage.prefabContentsRoot.transform is RectTransform prefabStageRoot )
			CheckRectTransformRecursive( prefabStageRoot, pointerPos, sceneView.camera, false, rootEntry.Children );
		else
		{
#if UNITY_2022_3_OR_NEWER
			Canvas[] canvases = FindObjectsByType<Canvas>( FindObjectsSortMode.None );
#else
			Canvas[] canvases = FindObjectsOfType<Canvas>();
#endif
			Array.Sort( canvases, ( c1, c2 ) => c1.sortingOrder.CompareTo( c2.sortingOrder ) );
			foreach( Canvas canvas in canvases )
			{
				if( canvas != null && canvas.gameObject.activeInHierarchy && canvas.isRootCanvas )
					CheckRectTransformRecursive( (RectTransform) canvas.transform, pointerPos, sceneView.camera, false, rootEntry.Children );
			}
		}

		// Remove non-Graphic root entries with no children from the results
		rootEntry.Children.RemoveAll( ( canvasEntry ) => canvasEntry.Children.Count == 0 && !canvasEntry.RectTransform.GetComponent<Graphic>() );

		// If any results found, show the context window
		if( rootEntry.Children.Count > 0 )
			CreateInstance<SceneViewUIObjectPickerContextWindow>().ShowContextWindow( rootEntry.Children );
	}

	private static void CheckRectTransformRecursive( RectTransform rectTransform, Vector2 pointerPos, Camera camera, bool culledByCanvasGroup, List<Entry> result )
	{
		Canvas canvas = rectTransform.GetComponent<Canvas>();
		if( canvas != null && !canvas.enabled )
			return;

		if( RectTransformUtility.RectangleContainsScreenPoint( rectTransform, pointerPos, camera ) && ShouldCheckRectTransform( rectTransform, pointerPos, camera, ref culledByCanvasGroup ) )
		{
			Entry entry = new Entry( rectTransform );
			result.Add( entry );
			result = entry.Children;
		}

		for( int i = 0, childCount = rectTransform.childCount; i < childCount; i++ )
		{
			RectTransform childRectTransform = rectTransform.GetChild( i ) as RectTransform;
			if( childRectTransform != null && childRectTransform.gameObject.activeSelf )
				CheckRectTransformRecursive( childRectTransform, pointerPos, camera, culledByCanvasGroup, result );
		}
	}

	private static bool ShouldCheckRectTransform( RectTransform rectTransform, Vector2 pointerPos, Camera camera, ref bool culledByCanvasGroup )
	{
		if( SceneVisibilityManager.instance.IsHidden( rectTransform.gameObject, false ) )
			return false;

		if( SceneVisibilityManager.instance.IsPickingDisabled( rectTransform.gameObject, false ) )
			return false;

		CanvasRenderer canvasRenderer = rectTransform.GetComponent<CanvasRenderer>();
		if( canvasRenderer != null && canvasRenderer.cull )
			return false;

		CanvasGroup canvasGroup = rectTransform.GetComponent<CanvasGroup>();
		if( canvasGroup != null )
		{
			if( canvasGroup.ignoreParentGroups )
				culledByCanvasGroup = canvasGroup.alpha == 0f;
			else if( canvasGroup.alpha == 0f )
				culledByCanvasGroup = true;
		}

		if( !culledByCanvasGroup )
		{
			// If the target is a MaskableGraphic that ignores masks (i.e. visible outside masks) and isn't fully transparent, accept it
			MaskableGraphic maskableGraphic = rectTransform.GetComponent<MaskableGraphic>();
			if( maskableGraphic != null && !maskableGraphic.maskable && maskableGraphic.color.a > 0f )
				return true;

			raycastFilters.Clear();
			rectTransform.GetComponentsInParent( false, raycastFilters );
			foreach( var raycastFilter in raycastFilters )
			{
				if( !raycastFilter.IsRaycastLocationValid( pointerPos, camera ) )
					return false;
			}
		}

		return !culledByCanvasGroup;
	}

    /// <summary>
    /// Restricts the given Rect within the screen's bounds.
    /// </summary>
    private static Rect GetScreenFittedRect(Rect originalRect, EditorWindow editorWindow)
    {
        screenFittedRectGetter ??= typeof(EditorWindow).Assembly.GetType("UnityEditor.ContainerWindow").GetMethod("FitRectToScreen", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

        if (screenFittedRectGetter.GetParameters().Length == 3)
            return (Rect)screenFittedRectGetter.Invoke(null, new object[] { originalRect, true, true });
        else
        {
            // New version introduced in Unity 2022.3.62f1, Unity 6.0.49f1 and Unity 6.1.0f1.
            // Usage example: https://github.com/Unity-Technologies/UnityCsReference/blob/10f8718268a7e34844ba7d59792117c28d75a99b/Editor/Mono/EditorWindow.cs#L1264
            editorWindowHostViewGetter ??= typeof(EditorWindow).GetField("m_Parent", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            hostViewContainerWindowGetter ??= typeof(EditorWindow).Assembly.GetType("UnityEditor.HostView").GetProperty("window", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            return (Rect)screenFittedRectGetter.Invoke(null, new object[] { originalRect, originalRect.center, true, hostViewContainerWindowGetter.GetValue(editorWindowHostViewGetter.GetValue(editorWindow), null) });
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值