using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Unity.Tutorials.Core.Editor
{
///
/// Utilities for EditorWindows.
///
public static class EditorWindowUtils
{
///
/// Finds the first open EditorWindow instance, if such exists.
///
///
///
public static T FindOpenInstance() where T : EditorWindow => Resources.FindObjectsOfTypeAll().FirstOrDefault();
///
/// Supported dock positions.
///
public enum DockPosition
{
///
/// Dock to the left side of the window.
///
Left,
///
/// Dock to the top of the window.
///
Top,
///
/// Dock to the right side of the window.
///
Right,
///
/// Dock to the bottom of the window.
///
Bottom
}
///
/// Docks the "docked" window to the "anchor" window at the given position.
///
/// Window to dock.
/// Window to dock into.
/// Position to the docked into.
public static void DockWindow(this EditorWindow anchor, EditorWindow docked, DockPosition position)
{
// NOTE Code adapted from https://gist.github.com/Thundernerd/5085ec29819b2960f5ff2ee32ad57cbb#gistcomment-2834853
var anchorParent = WindowLayoutProxy.GetParentOf(anchor);
SetDragSource(anchorParent, WindowLayoutProxy.GetParentOf(docked));
WindowLayoutProxy.PerformDrop(anchorParent, docked, GetFakeMousePosition(anchor, position));
}
///
/// Centers an EditorWindow to the Editor main window.
///
///
public static void CenterOnMainWindow(EditorWindow win)
{
var main = GetEditorMainWindowPos();
var pos = win.position;
float w = (main.width - pos.width) * 0.5f;
float h = (main.height - pos.height) * 0.5f;
pos.x = main.x + w;
pos.y = main.y + h;
win.position = pos;
}
///
/// Returns the position of the Editor main window.
///
///
public static Rect GetEditorMainWindowPos()
{
// NOTE Code adapted from http://answers.unity.com/answers/960709/view.html
var containerWinType = GetAllDerivedTypes(AppDomain.CurrentDomain, typeof(ScriptableObject))
.Where(t => t.Name == "ContainerWindow")
.FirstOrDefault();
if (containerWinType == null)
throw new MissingMemberException("Can't find internal type ContainerWindow. Maybe something has changed inside Unity");
var showModeField = containerWinType.GetField("m_ShowMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var positionProperty = containerWinType.GetProperty("position", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (showModeField == null || positionProperty == null)
throw new MissingFieldException("Can't find internal fields 'm_ShowMode' or 'position'. Maybe something has changed inside Unity");
foreach (var win in Resources.FindObjectsOfTypeAll(containerWinType))
{
var showmode = (int)showModeField.GetValue(win);
if (showmode == 4) // main window
{
var pos = (Rect)positionProperty.GetValue(win, null);
return pos;
}
}
throw new NotSupportedException("Can't find internal main window. Maybe something has changed inside Unity");
}
///
/// Sets the position of the Editor main window.
///
///
public static void SetEditorMainWindowPos(Rect pos)
{
// TODO copy-pasta, generalise and clean up the code with GetEditorMainWindowPos
var containerWinType = GetAllDerivedTypes(AppDomain.CurrentDomain, typeof(ScriptableObject))
.Where(t => t.Name == "ContainerWindow")
.FirstOrDefault();
if (containerWinType == null)
throw new MissingMemberException("Can't find internal type ContainerWindow. Maybe something has changed inside Unity");
var showModeField = containerWinType.GetField("m_ShowMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var positionProperty = containerWinType.GetProperty("position", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (showModeField == null || positionProperty == null)
throw new MissingFieldException("Can't find internal fields 'm_ShowMode' or 'position'. Maybe something has changed inside Unity");
foreach (var win in Resources.FindObjectsOfTypeAll(containerWinType))
{
var showmode = (int)showModeField.GetValue(win);
if (showmode == 4) // main window
{
positionProperty.SetValue(win, pos);
return;
}
}
throw new NotSupportedException("Can't find internal main window. Maybe something has changed inside Unity");
}
static IEnumerable GetAllDerivedTypes(AppDomain appDomain, Type parentType)
{
return appDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.IsSubclassOf(parentType));
}
static EditorWindowUtils()
{
// Assertions to surface potential reflection problems as soon as possible.
// These should make Package Validation Suite's EmptyConsoleTest to fail in case
// the internal APIs would change.
// TODO assert types used by GetEditorMainWindowPos() also.
// DockArea
var type = Type.GetType("UnityEditor.DockArea, UnityEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
Debug.Assert(type != null);
Debug.Assert(
type.GetField("s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic) != null,
"Internal API DockArea.s_OriginalDragSource missing."
);
}
static Vector2 GetFakeMousePosition(EditorWindow window, 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.
const float offset = 20;
switch (position)
{
case DockPosition.Left:
mousePosition.Set(offset, window.position.size.y / 2);
break;
case DockPosition.Top:
// Top docking seems to require roughly a double offset in order to work,
// probably due to extra space required by the tab bar.
mousePosition.Set(window.position.size.x / 2, offset * 2);
break;
case DockPosition.Right:
mousePosition.Set(window.position.size.x - offset, window.position.size.y / 2);
break;
case DockPosition.Bottom:
mousePosition.Set(window.position.size.x / 2, window.position.size.y - offset);
break;
}
return new Vector2(window.position.x + mousePosition.x, window.position.y + mousePosition.y);
}
static void SetDragSource(object target, object source)
{
var field = target.GetType().GetField("s_OriginalDragSource", BindingFlags.Static | BindingFlags.NonPublic);
field.SetValue(null, source);
}
}
}