Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save savage69kr/e7f1b9cc9f7f7df751f3f435123a2f9f to your computer and use it in GitHub Desktop.
Save savage69kr/e7f1b9cc9f7f7df751f3f435123a2f9f to your computer and use it in GitHub Desktop.

Revisions

  1. @yasirkula yasirkula revised this gist Dec 9, 2023. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions SceneViewUIObjectPickerContextWindow.cs
    Original file line number Diff line number Diff line change
    @@ -252,6 +252,11 @@ private static bool ShouldCheckRectTransform( RectTransform rectTransform, Vecto

    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 )
  2. @yasirkula yasirkula revised this gist Dec 8, 2023. 1 changed file with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions SceneViewUIObjectPickerContextWindow.cs
    Original file line number Diff line number Diff line change
    @@ -229,6 +229,14 @@ private static void CheckRectTransformRecursive( RectTransform rectTransform, Ve

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

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

    CanvasRenderer canvasRenderer = rectTransform.GetComponent<CanvasRenderer>();
    if( canvasRenderer != null && canvasRenderer.cull )
    return false;
  3. @yasirkula yasirkula created this gist Dec 2, 2023.
    258 changes: 258 additions & 0 deletions SceneViewUIObjectPickerContextWindow.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,258 @@
    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 readonly MethodInfo screenFittedRectGetter = typeof( EditorWindow ).Assembly.GetType( "UnityEditor.ContainerWindow" ).GetMethod( "FitRectToScreen", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );

    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 ) );

    // Show dropdown above the cursor instead of below the cursor
    position = (Rect) screenFittedRectGetter.Invoke( null, new object[3] { new Rect( GUIUtility.GUIToScreenPoint( Event.current.mousePosition ) - new Vector2( 0f, position.height ), position.size ), true, true } );
    }

    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;
    #if UNITY_2020_1_OR_NEWER
    wantsLessLayoutEvents = false;
    #endif
    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()
    {
    #if UNITY_2019_1_OR_NEWER
    SceneView.duringSceneGui += ( SceneView sceneView ) =>
    #else
    SceneView.onSceneGUIDelegate += ( SceneView sceneView ) =>
    #endif
    {
    /// 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 );
    #if UNITY_2018_3_OR_NEWER
    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
    #endif
    {
    #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 )
    {
    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 )
    {
    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 )
    {
    raycastFilters.Clear();
    rectTransform.GetComponentsInParent( false, raycastFilters );
    foreach( var raycastFilter in raycastFilters )
    {
    if( !raycastFilter.IsRaycastLocationValid( pointerPos, camera ) )
    return false;
    }
    }

    return !culledByCanvasGroup;
    }
    }