using System;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace EnhancedHierarchy {
///
/// Misc utilities for Enhanced Hierarchy.
///
public static class Utility {
private const string CTRL = "Ctrl";
private const string CMD = "Cmd";
private const string MENU_ITEM_PATH = "Edit/Enhanced Hierarchy %h";
private static int errorCount;
private static readonly GUIContent tempContent = new GUIContent();
public static string CtrlKey { get { return Application.platform == RuntimePlatform.OSXEditor ? CMD : CTRL; } }
[MenuItem(MENU_ITEM_PATH, false, int.MinValue)]
private static void EnableDisableHierarchy() {
Preferences.Enabled.Value = !Preferences.Enabled;
EditorApplication.RepaintHierarchyWindow();
}
[MenuItem(MENU_ITEM_PATH, true)]
private static bool CheckHierarchyEnabled() {
Menu.SetChecked(MENU_ITEM_PATH, Preferences.Enabled);
return true;
}
public static void EnableFPSCounter() {
var frames = 0;
var fps = 0d;
var lastTime = 0d;
var content = new GUIContent();
var evt = EventType.Repaint;
EditorApplication.hierarchyWindowItemOnGUI += (id, rect) => {
using(ProfilerSample.Get("Enhanced Hierarchy"))
using(ProfilerSample.Get("FPS Counter")) {
if (evt == Event.current.type)
return;
evt = Event.current.type;
if (evt == EventType.Repaint)
frames++;
if (EditorApplication.timeSinceStartup - lastTime < 0.5d)
return;
fps = frames / (EditorApplication.timeSinceStartup - lastTime);
lastTime = EditorApplication.timeSinceStartup;
frames = 0;
content.text = string.Format("{0:00.0} FPS", fps);
content.image = Styles.warningIcon;
SetHierarchyTitle(content);
}
};
}
public static bool ShouldCalculateTooltipAt(Rect area) {
return area.Contains(Event.current.mousePosition);
}
public static void ForceUpdateHierarchyEveryFrame() {
// EditorApplication.update += () => {
// if(EditorWindow.mouseOverWindow)
// EditorApplication.RepaintHierarchyWindow();
// };
}
public static void LogException(Exception e) {
Debug.LogError("Unexpected exception in Enhanced Hierarchy");
Debug.LogException(e);
if (errorCount++ >= 10) {
Debug.LogWarning("Automatically disabling Enhanced Hierarchy, if the error persists contact the developer");
Preferences.Enabled.Value = false;
errorCount = 0;
if (!EditorPrefs.GetBool("EHEmailAskDisabled", false))
switch (EditorUtility.DisplayDialogComplex("Mail Developer", "Enhanced Hierarchy has found an exeption, would you like to report a bug to the developer? (If you choose yes your mail app will open with a few techinical information)", "Yes", "Not now", "No and don't ask again")) {
case 0:
Preferences.OpenSupportEmail(e);
EditorUtility.DisplayDialog("Mail Developer", "Your mail app will open now, if it doesn't please send an email reporting the bug to " + Preferences.DEVELOPER_EMAIL, "OK");
break;
case 1:
break;
case 2:
EditorPrefs.SetBool("EHEmailAskDisabled", true);
EditorUtility.DisplayDialog("Mail Developer", "You won't be bothered again, sorry", "OK");
break;
}
}
}
public static void SetHierarchyTitle(string title) {
try {
Reflected.HierarchyWindowInstance.titleContent.text = title;
} catch (Exception e) {
Debug.LogWarning("Failed to set hierarchy title: " + e);
}
}
public static void SetHierarchyTitle(GUIContent content) {
try {
Reflected.HierarchyWindowInstance.titleContent = content;
} catch (Exception e) {
Debug.LogWarning("Failed to set hierarchy title: " + e);
}
}
public static GUIStyle CreateStyleFromTextures(Texture2D on, Texture2D off) {
return CreateStyleFromTextures(null, on, off);
}
public static GUIStyle CreateStyleFromTextures(GUIStyle reference, Texture2D on, Texture2D off) {
using(ProfilerSample.Get()) {
var style = reference != null ? new GUIStyle(reference) : new GUIStyle();
style.active.background = off;
style.focused.background = off;
style.hover.background = off;
style.normal.background = off;
style.onActive.background = on;
style.onFocused.background = on;
style.onHover.background = on;
style.onNormal.background = on;
style.imagePosition = ImagePosition.ImageOnly;
EditorApplication.update += () => {
style.fixedHeight = Preferences.IconsSize;
style.fixedWidth = Preferences.IconsSize;
};
return style;
}
}
public static Texture2D GetBackground(GUIStyle style, bool on) {
return on ?
style.onNormal.background :
style.normal.background;
}
public static Texture2D FindOrLoad(string base64) {
var name = string.Format("Enhanced_Hierarchy_{0}", (long)base64.GetHashCode() - int.MinValue);
return FindTextureFromName(name) ?? LoadTexture(base64, name);
}
public static Texture2D LoadTexture(string base64, string name) {
using(ProfilerSample.Get())
try {
var bytes = Convert.FromBase64String(base64);
var texture = new Texture2D(0, 0, TextureFormat.ARGB32, false, false);
texture.name = name;
texture.hideFlags = HideFlags.HideAndDontSave;
texture.LoadImage(bytes);
return texture;
} catch (Exception e) {
Debug.LogErrorFormat("Failed to load texture \"{0}\": {1}", name, e);
return null;
}
}
public static Texture2D FindTextureFromName(string name) {
using(ProfilerSample.Get())
try {
var textures = Resources.FindObjectsOfTypeAll();
for (var i = 0; i < textures.Length; i++)
if (textures[i].name == name)
return textures[i];
return null;
} catch (Exception e) {
Debug.LogErrorFormat("Failed to find texture \"{0}\": {1}", name, e);
return null;
}
}
public static Color GetHierarchyColor(Transform t) {
if (!t)
return Color.clear;
return GetHierarchyColor(t.gameObject);
}
public static Color GetHierarchyColor(GameObject go) {
if (!go)
return Color.black;
return GetHierarchyLabelStyle(go).normal.textColor;
}
public static GUIStyle GetHierarchyLabelStyle(GameObject go) {
using(ProfilerSample.Get()) {
if (!go)
return EditorStyles.label;
var active = go.activeInHierarchy;
#if UNITY_2018_3_OR_NEWER
var prefabType = PrefabUtility.GetPrefabInstanceStatus(go);
switch (prefabType) {
case PrefabInstanceStatus.Connected:
return active ? Styles.labelPrefab : Styles.labelPrefabDisabled;
case PrefabInstanceStatus.MissingAsset:
return active ? Styles.labelPrefabBroken : Styles.labelPrefabBrokenDisabled;
default:
return active ? Styles.labelNormal : Styles.labelDisabled;
}
#else
var prefabType = PrefabUtility.GetPrefabType(PrefabUtility.FindPrefabRoot(go));
switch (prefabType) {
case PrefabType.PrefabInstance:
case PrefabType.ModelPrefabInstance:
return active ? Styles.labelPrefab : Styles.labelPrefabDisabled;
case PrefabType.MissingPrefabInstance:
return active ? Styles.labelPrefabBroken : Styles.labelPrefabBrokenDisabled;
default:
return active ? Styles.labelNormal : Styles.labelDisabled;
}
#endif
}
}
public static Color OverlayColors(Color src, Color dst) {
using(ProfilerSample.Get()) {
var alpha = dst.a + src.a * (1f - dst.a);
var result = (dst * dst.a + src * src.a * (1f - dst.a)) / alpha;
result.a = alpha;
return result;
}
}
public static bool TransformIsLastChild(Transform t) {
using(ProfilerSample.Get()) {
if (!t)
return true;
return t.GetSiblingIndex() == t.parent.childCount - 1;
}
}
public static void ApplyHideFlagsToPrefab(UnityEngine.Object obj) {
var handle = PrefabUtility.GetPrefabInstanceHandle(obj);
if (handle)
handle.hideFlags = obj.hideFlags;
}
public static void LockObject(GameObject go) {
using(ProfilerSample.Get()) {
go.hideFlags |= HideFlags.NotEditable;
ApplyHideFlagsToPrefab(go);
#if UNITY_2019_3_OR_NEWER
if (!Preferences.AllowPickingLockedObjects)
SceneVisibilityManager.instance.DisablePicking(go, false);
#endif
EditorUtility.SetDirty(go);
}
}
public static void UnlockObject(GameObject go) {
using(ProfilerSample.Get()) {
go.hideFlags &= ~HideFlags.NotEditable;
ApplyHideFlagsToPrefab(go);
#if UNITY_2019_3_OR_NEWER
if (!Preferences.AllowPickingLockedObjects)
SceneVisibilityManager.instance.EnablePicking(go, false);
#endif
EditorUtility.SetDirty(go);
}
}
// public static void UnlockAllObjects() {
// using(ProfilerSample.Get())
// foreach (var objRaw in Resources.FindObjectsOfTypeAll()) {
// var obj = ObjectOrPrefabInstanceHandle(objRaw);
// if (obj && (obj.hideFlags & HideFlags.HideInHierarchy) == 0 && !EditorUtility.IsPersistent(obj))
// UnlockObject(obj);
// }
// }
public static void ApplyPrefabModifications(GameObject go, bool allowCreatingNew) {
#if UNITY_2018_3_OR_NEWER
var isPrefab = PrefabUtility.IsPartOfAnyPrefab(go);
#else
var isPrefab = PrefabUtility.GetPrefabType(go) == PrefabType.PrefabInstance;
#endif
if (isPrefab) {
#if UNITY_2018_3_OR_NEWER
var prefab = PrefabUtility.GetNearestPrefabInstanceRoot(go);
#elif UNITY_2018_2_OR_NEWER
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
#else
var prefab = PrefabUtility.GetPrefabParent(go);
#endif
if (!prefab) {
Debug.LogError("Prefab asset not valid!");
return;
}
#if UNITY_2018_3_OR_NEWER
if (PrefabUtility.GetPrefabInstanceStatus(prefab) == PrefabInstanceStatus.Connected)
PrefabUtility.ApplyPrefabInstance(prefab, InteractionMode.UserAction);
else if (EditorUtility.DisplayDialog("Apply disconnected prefab", "This is a disconnected game object, do you want to try to reconnect to the last prefab asset?", "Try to Reconnect", "Cancel"))
PrefabUtility.RevertPrefabInstance(prefab, InteractionMode.UserAction);
EditorUtility.SetDirty(prefab);
#else
var selection = Selection.instanceIDs;
Selection.activeGameObject = go;
if (EditorApplication.ExecuteMenuItem("GameObject/Apply Changes To Prefab")) {
EditorUtility.SetDirty(prefab);
Selection.instanceIDs = selection;
} else
Debug.LogError("Failed to apply prefab modifications");
#endif
} else if (allowCreatingNew) {
var path = EditorUtility.SaveFilePanelInProject("Save prefab", "New Prefab", "prefab", "Save the selected prefab");
if (!string.IsNullOrEmpty(path))
#if UNITY_2018_3_OR_NEWER
PrefabUtility.SaveAsPrefabAssetAndConnect(go, path, InteractionMode.UserAction);
#else
PrefabUtility.CreatePrefab(path, go, ReplacePrefabOptions.ConnectToPrefab);
#endif
}
}
public static string EnumFlagsToString(Enum value) {
using(ProfilerSample.Get())
try {
if ((int)(object)value == -1)
return "Everything";
var str = new StringBuilder();
var separator = ", ";
foreach (var enumValue in Enum.GetValues(value.GetType())) {
var i = (int)enumValue;
if (i != 0 && (i & (i - 1)) == 0 && Enum.IsDefined(value.GetType(), i) && (Convert.ToInt32(value) & i) != 0) {
str.Append(ObjectNames.NicifyVariableName(enumValue.ToString()));
str.Append(separator);
}
}
if (str.Length > 0)
str.Length -= separator.Length;
return str.ToString();
} catch (Exception e) {
if (Preferences.DebugEnabled)
Debug.LogException(e);
return string.Empty;
}
}
public static GUIContent GetTempGUIContent(string text, string tooltip = null, Texture2D image = null) {
tempContent.text = text;
tempContent.tooltip = tooltip;
tempContent.image = image;
return tempContent;
}
public static string SafeGetName(this IconBase icon) {
try {
return icon.Name;
} catch (Exception e) {
Debug.LogException(e);
Preferences.ForceDisableButton(icon);
return string.Empty;
}
}
public static float SafeGetWidth(this IconBase icon) {
try {
return icon.Width + (Preferences.IconsSize - 15) / 2;
} catch (Exception e) {
Debug.LogException(e);
Preferences.ForceDisableButton(icon);
return 0f;
}
}
public static void SafeInit(this IconBase icon) {
try {
icon.Init();
} catch (Exception e) {
Debug.LogException(e);
Preferences.ForceDisableButton(icon);
}
}
public static void SafeDoGUI(this IconBase icon, Rect rect) {
try {
rect.yMin -= (Preferences.IconsSize - 15) / 2;
rect.xMin -= (Preferences.IconsSize - 15) / 2;
icon.DoGUI(rect);
} catch (Exception e) {
Debug.LogException(e);
Preferences.ForceDisableButton(icon);
}
}
public static Rect FlipRectHorizontally(Rect rect) {
return Rect.MinMaxRect(
rect.xMax,
rect.yMin,
rect.xMin,
rect.yMax
);
}
}
}