Skip to content

Instantly share code, notes, and snippets.

@Thundernerd
Last active March 21, 2025 17:28
Show Gist options
  • Save Thundernerd/5085ec29819b2960f5ff2ee32ad57cbb to your computer and use it in GitHub Desktop.
Save Thundernerd/5085ec29819b2960f5ff2ee32ad57cbb to your computer and use it in GitHub Desktop.
Helper to dock EditorWindows
#if UNITY_EDITOR
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public static class Docker
{
#region Reflection Types
private class _EditorWindow
{
private EditorWindow instance;
private Type type;
public _EditorWindow( EditorWindow instance ) {
this.instance = instance;
type = instance.GetType();
}
public object m_Parent {
get {
var field = type.GetField( "m_Parent", BindingFlags.Instance | BindingFlags.NonPublic );
return field.GetValue( instance );
}
}
}
private class _DockArea
{
private object instance;
private Type type;
public _DockArea( object instance ) {
this.instance = instance;
type = instance.GetType();
}
public object window {
get {
var property = type.GetProperty( "window", BindingFlags.Instance | BindingFlags.Public );
return property.GetValue( instance, null );
}
}
public object s_OriginalDragSource {
set {
var field = type.GetField( "s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic );
field.SetValue( null, value );
}
}
}
private class _ContainerWindow
{
private object instance;
private Type type;
public _ContainerWindow( object instance ) {
this.instance = instance;
type = instance.GetType();
}
public object rootSplitView {
get {
var property = type.GetProperty( "rootSplitView", BindingFlags.Instance | BindingFlags.Public );
return property.GetValue( instance, null );
}
}
}
private class _SplitView
{
private object instance;
private Type type;
public _SplitView( object instance ) {
this.instance = instance;
type = instance.GetType();
}
public object DragOver( EditorWindow child, Vector2 screenPoint ) {
var method = type.GetMethod( "DragOver", BindingFlags.Instance | BindingFlags.Public );
return method.Invoke( instance, new object[] { child, screenPoint } );
}
public void PerformDrop( EditorWindow child, object dropInfo, Vector2 screenPoint ) {
var method = type.GetMethod( "PerformDrop", BindingFlags.Instance | BindingFlags.Public );
method.Invoke( instance, new object[] { child, dropInfo, screenPoint } );
}
}
#endregion
public enum DockPosition
{
Left,
Top,
Right,
Bottom
}
/// <summary>
/// Docks the second window to the first window at the given position
/// </summary>
public static void Dock( this EditorWindow wnd, EditorWindow other, DockPosition position ) {
var mousePosition = GetFakeMousePosition( wnd, position );
var parent = new _EditorWindow( wnd );
var child = new _EditorWindow( other );
var dockArea = new _DockArea( parent.m_Parent );
var containerWindow = new _ContainerWindow( dockArea.window );
var splitView = new _SplitView( containerWindow.rootSplitView );
var dropInfo = splitView.DragOver( other, mousePosition );
dockArea.s_OriginalDragSource = child.m_Parent;
splitView.PerformDrop( other, dropInfo, mousePosition );
}
private static Vector2 GetFakeMousePosition( EditorWindow wnd, DockPosition position ) {
Vector2 mousePosition = Vector2.zero;
// The 20 is required to make the docking work.
// Smaller values might not work when faking the mouse position.
switch ( position ) {
case DockPosition.Left:
mousePosition = new Vector2( 20, wnd.position.size.y / 2 );
break;
case DockPosition.Top:
mousePosition = new Vector2( wnd.position.size.x / 2, 20 );
break;
case DockPosition.Right:
mousePosition = new Vector2( wnd.position.size.x - 20, wnd.position.size.y / 2 );
break;
case DockPosition.Bottom:
mousePosition = new Vector2( wnd.position.size.x / 2, wnd.position.size.y - 20 );
break;
}
return GUIUtility.GUIToScreenPoint( mousePosition );
}
}
#endif
Copy link

ghost commented Feb 27, 2023

Some advice for astronauts: you can use internal bridges of unity: https://github.com/Unity-Technologies/UnityCsReference/blob/a6cadb936f3855ab7e5bd8e19d85af403d6802c6/Editor/Mono/AssemblyInfo/AssemblyInfo.cs
Just create an assembly with the one of the names (Unity.InternalAPIEditorBridge.001 for example) and then you can use internals of unity inside this assebly. You don't need reflection afrer that

       public static void DockTo(this EditorWindow first, EditorWindow second, DockPosition position)
        {
            Vector2 mousePosition = GetFakeMousePosition(second, position);
            SplitView targetView = null;
            DropInfo dropInfo = null;
            var windows = ContainerWindow.windows;
            for (int i = 0; i < windows.Length; i++)
            {
                SplitView rootSplitView = windows[i].rootSplitView;
                if (rootSplitView != null)
                {
                    dropInfo = rootSplitView.DragOverRootView(mousePosition);
                    targetView = rootSplitView;
                }

                if (dropInfo == null)
                {
                    View rootView = windows[i].rootView;
                    for (int j = 0; j < rootView.allChildren.Length; j++)
                    {
                        if (rootView.allChildren[j] is IDropArea dropArea)
                        {
                            dropInfo = dropArea.DragOver(second, mousePosition);
                            if (dropInfo != null)
                            {
                                targetView = rootView.allChildren[j] as SplitView;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    break;
                }
            }


            if (targetView != null && dropInfo != null)
            {
                DockArea.s_OriginalDragSource = (DockArea) first.m_Parent;
                targetView.PerformDrop(first, dropInfo, mousePosition);
            }
        }

@jungley
Copy link

jungley commented Jan 11, 2025

If anyone needs an example of how to use the Docker class,
you'll need a launcher window to dock the other windows.

Like the earlier post mentioned make sure update Docker to..

Change last line of GetFakeMousePosition() from:
return GUIUtility.GUIToScreenPoint( mousePosition );
to
return new Vector2(wnd.position.x + mousePosition.x, wnd.position.y + mousePosition.y);

public class CustomEditorWindowLauncher : EditorWindow
{
    public EditorWindow01 window1;
    public EditorWindow02 window2;

    //[MenuItem("Window/Launch Side by Side")]
    public static void LaunchWindows1()
    {
        var launcher = CreateInstance<CustomEditorWindowLauncher>();
        launcher.Show();

        launcher.window1 = EditorWindow.GetWindow<EditorWindow01>();
        launcher.window1.titleContent = new GUIContent("Editor Window 01");

        launcher.window2 = EditorWindow.GetWindow<EditorWindow02>();
        launcher.window2.titleContent = new GUIContent("Editor Window 02");
    }

    void OnGUI()
    {
           Docker.Dock(window1, window2, Docker.DockPosition.Right);
           //Close the launcher
          Close();
    }
}

Hope this helps

@OrangeZzzzzz
Copy link

I found that dockWindow works successfully only if dropInfo is not null,I fixed the code like this

private void OnGUI()
{
    if( m_Edit.DockWindow( m_Preview, Docker.DockPosition.Left ) )
    {
        Close();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment