Utility.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. using System;
  2. using System.Linq;
  3. using System.Text;
  4. using UnityEditor;
  5. using UnityEngine;
  6. namespace EnhancedHierarchy {
  7. /// <summary>
  8. /// Misc utilities for Enhanced Hierarchy.
  9. /// </summary>
  10. public static class Utility {
  11. private const string CTRL = "Ctrl";
  12. private const string CMD = "Cmd";
  13. private const string MENU_ITEM_PATH = "Edit/Enhanced Hierarchy %h";
  14. private static int errorCount;
  15. private static readonly GUIContent tempContent = new GUIContent();
  16. public static string CtrlKey { get { return Application.platform == RuntimePlatform.OSXEditor ? CMD : CTRL; } }
  17. [MenuItem(MENU_ITEM_PATH, false, int.MinValue)]
  18. private static void EnableDisableHierarchy() {
  19. Preferences.Enabled.Value = !Preferences.Enabled;
  20. EditorApplication.RepaintHierarchyWindow();
  21. }
  22. [MenuItem(MENU_ITEM_PATH, true)]
  23. private static bool CheckHierarchyEnabled() {
  24. Menu.SetChecked(MENU_ITEM_PATH, Preferences.Enabled);
  25. return true;
  26. }
  27. public static void EnableFPSCounter() {
  28. var frames = 0;
  29. var fps = 0d;
  30. var lastTime = 0d;
  31. var content = new GUIContent();
  32. var evt = EventType.Repaint;
  33. EditorApplication.hierarchyWindowItemOnGUI += (id, rect) => {
  34. using(ProfilerSample.Get("Enhanced Hierarchy"))
  35. using(ProfilerSample.Get("FPS Counter")) {
  36. if (evt == Event.current.type)
  37. return;
  38. evt = Event.current.type;
  39. if (evt == EventType.Repaint)
  40. frames++;
  41. if (EditorApplication.timeSinceStartup - lastTime < 0.5d)
  42. return;
  43. fps = frames / (EditorApplication.timeSinceStartup - lastTime);
  44. lastTime = EditorApplication.timeSinceStartup;
  45. frames = 0;
  46. content.text = string.Format("{0:00.0} FPS", fps);
  47. content.image = Styles.warningIcon;
  48. SetHierarchyTitle(content);
  49. }
  50. };
  51. }
  52. public static bool ShouldCalculateTooltipAt(Rect area) {
  53. return area.Contains(Event.current.mousePosition);
  54. }
  55. public static void ForceUpdateHierarchyEveryFrame() {
  56. // EditorApplication.update += () => {
  57. // if(EditorWindow.mouseOverWindow)
  58. // EditorApplication.RepaintHierarchyWindow();
  59. // };
  60. }
  61. public static void LogException(Exception e) {
  62. Debug.LogError("Unexpected exception in Enhanced Hierarchy");
  63. Debug.LogException(e);
  64. if (errorCount++ >= 10) {
  65. Debug.LogWarning("Automatically disabling Enhanced Hierarchy, if the error persists contact the developer");
  66. Preferences.Enabled.Value = false;
  67. errorCount = 0;
  68. if (!EditorPrefs.GetBool("EHEmailAskDisabled", false))
  69. 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")) {
  70. case 0:
  71. Preferences.OpenSupportEmail(e);
  72. 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");
  73. break;
  74. case 1:
  75. break;
  76. case 2:
  77. EditorPrefs.SetBool("EHEmailAskDisabled", true);
  78. EditorUtility.DisplayDialog("Mail Developer", "You won't be bothered again, sorry", "OK");
  79. break;
  80. }
  81. }
  82. }
  83. public static void SetHierarchyTitle(string title) {
  84. try {
  85. Reflected.HierarchyWindowInstance.titleContent.text = title;
  86. } catch (Exception e) {
  87. Debug.LogWarning("Failed to set hierarchy title: " + e);
  88. }
  89. }
  90. public static void SetHierarchyTitle(GUIContent content) {
  91. try {
  92. Reflected.HierarchyWindowInstance.titleContent = content;
  93. } catch (Exception e) {
  94. Debug.LogWarning("Failed to set hierarchy title: " + e);
  95. }
  96. }
  97. public static GUIStyle CreateStyleFromTextures(Texture2D on, Texture2D off) {
  98. return CreateStyleFromTextures(null, on, off);
  99. }
  100. public static GUIStyle CreateStyleFromTextures(GUIStyle reference, Texture2D on, Texture2D off) {
  101. using(ProfilerSample.Get()) {
  102. var style = reference != null ? new GUIStyle(reference) : new GUIStyle();
  103. style.active.background = off;
  104. style.focused.background = off;
  105. style.hover.background = off;
  106. style.normal.background = off;
  107. style.onActive.background = on;
  108. style.onFocused.background = on;
  109. style.onHover.background = on;
  110. style.onNormal.background = on;
  111. style.imagePosition = ImagePosition.ImageOnly;
  112. EditorApplication.update += () => {
  113. style.fixedHeight = Preferences.IconsSize;
  114. style.fixedWidth = Preferences.IconsSize;
  115. };
  116. return style;
  117. }
  118. }
  119. public static Texture2D GetBackground(GUIStyle style, bool on) {
  120. return on ?
  121. style.onNormal.background :
  122. style.normal.background;
  123. }
  124. public static Texture2D FindOrLoad(string base64) {
  125. var name = string.Format("Enhanced_Hierarchy_{0}", (long)base64.GetHashCode() - int.MinValue);
  126. return FindTextureFromName(name) ?? LoadTexture(base64, name);
  127. }
  128. public static Texture2D LoadTexture(string base64, string name) {
  129. using(ProfilerSample.Get())
  130. try {
  131. var bytes = Convert.FromBase64String(base64);
  132. var texture = new Texture2D(0, 0, TextureFormat.ARGB32, false, false);
  133. texture.name = name;
  134. texture.hideFlags = HideFlags.HideAndDontSave;
  135. texture.LoadImage(bytes);
  136. return texture;
  137. } catch (Exception e) {
  138. Debug.LogErrorFormat("Failed to load texture \"{0}\": {1}", name, e);
  139. return null;
  140. }
  141. }
  142. public static Texture2D FindTextureFromName(string name) {
  143. using(ProfilerSample.Get())
  144. try {
  145. var textures = Resources.FindObjectsOfTypeAll<Texture2D>();
  146. for (var i = 0; i < textures.Length; i++)
  147. if (textures[i].name == name)
  148. return textures[i];
  149. return null;
  150. } catch (Exception e) {
  151. Debug.LogErrorFormat("Failed to find texture \"{0}\": {1}", name, e);
  152. return null;
  153. }
  154. }
  155. public static Color GetHierarchyColor(Transform t) {
  156. if (!t)
  157. return Color.clear;
  158. return GetHierarchyColor(t.gameObject);
  159. }
  160. public static Color GetHierarchyColor(GameObject go) {
  161. if (!go)
  162. return Color.black;
  163. return GetHierarchyLabelStyle(go).normal.textColor;
  164. }
  165. public static GUIStyle GetHierarchyLabelStyle(GameObject go) {
  166. using(ProfilerSample.Get()) {
  167. if (!go)
  168. return EditorStyles.label;
  169. var active = go.activeInHierarchy;
  170. #if UNITY_2018_3_OR_NEWER
  171. var prefabType = PrefabUtility.GetPrefabInstanceStatus(go);
  172. switch (prefabType) {
  173. case PrefabInstanceStatus.Connected:
  174. return active ? Styles.labelPrefab : Styles.labelPrefabDisabled;
  175. case PrefabInstanceStatus.MissingAsset:
  176. return active ? Styles.labelPrefabBroken : Styles.labelPrefabBrokenDisabled;
  177. default:
  178. return active ? Styles.labelNormal : Styles.labelDisabled;
  179. }
  180. #else
  181. var prefabType = PrefabUtility.GetPrefabType(PrefabUtility.FindPrefabRoot(go));
  182. switch (prefabType) {
  183. case PrefabType.PrefabInstance:
  184. case PrefabType.ModelPrefabInstance:
  185. return active ? Styles.labelPrefab : Styles.labelPrefabDisabled;
  186. case PrefabType.MissingPrefabInstance:
  187. return active ? Styles.labelPrefabBroken : Styles.labelPrefabBrokenDisabled;
  188. default:
  189. return active ? Styles.labelNormal : Styles.labelDisabled;
  190. }
  191. #endif
  192. }
  193. }
  194. public static Color OverlayColors(Color src, Color dst) {
  195. using(ProfilerSample.Get()) {
  196. var alpha = dst.a + src.a * (1f - dst.a);
  197. var result = (dst * dst.a + src * src.a * (1f - dst.a)) / alpha;
  198. result.a = alpha;
  199. return result;
  200. }
  201. }
  202. public static bool TransformIsLastChild(Transform t) {
  203. using(ProfilerSample.Get()) {
  204. if (!t)
  205. return true;
  206. return t.GetSiblingIndex() == t.parent.childCount - 1;
  207. }
  208. }
  209. public static void ApplyHideFlagsToPrefab(UnityEngine.Object obj) {
  210. var handle = PrefabUtility.GetPrefabInstanceHandle(obj);
  211. if (handle)
  212. handle.hideFlags = obj.hideFlags;
  213. }
  214. public static void LockObject(GameObject go) {
  215. using(ProfilerSample.Get()) {
  216. go.hideFlags |= HideFlags.NotEditable;
  217. ApplyHideFlagsToPrefab(go);
  218. #if UNITY_2019_3_OR_NEWER
  219. if (!Preferences.AllowPickingLockedObjects)
  220. SceneVisibilityManager.instance.DisablePicking(go, false);
  221. #endif
  222. EditorUtility.SetDirty(go);
  223. }
  224. }
  225. public static void UnlockObject(GameObject go) {
  226. using(ProfilerSample.Get()) {
  227. go.hideFlags &= ~HideFlags.NotEditable;
  228. ApplyHideFlagsToPrefab(go);
  229. #if UNITY_2019_3_OR_NEWER
  230. if (!Preferences.AllowPickingLockedObjects)
  231. SceneVisibilityManager.instance.EnablePicking(go, false);
  232. #endif
  233. EditorUtility.SetDirty(go);
  234. }
  235. }
  236. // public static void UnlockAllObjects() {
  237. // using(ProfilerSample.Get())
  238. // foreach (var objRaw in Resources.FindObjectsOfTypeAll<GameObject>()) {
  239. // var obj = ObjectOrPrefabInstanceHandle(objRaw);
  240. // if (obj && (obj.hideFlags & HideFlags.HideInHierarchy) == 0 && !EditorUtility.IsPersistent(obj))
  241. // UnlockObject(obj);
  242. // }
  243. // }
  244. public static void ApplyPrefabModifications(GameObject go, bool allowCreatingNew) {
  245. #if UNITY_2018_3_OR_NEWER
  246. var isPrefab = PrefabUtility.IsPartOfAnyPrefab(go);
  247. #else
  248. var isPrefab = PrefabUtility.GetPrefabType(go) == PrefabType.PrefabInstance;
  249. #endif
  250. if (isPrefab) {
  251. #if UNITY_2018_3_OR_NEWER
  252. var prefab = PrefabUtility.GetNearestPrefabInstanceRoot(go);
  253. #elif UNITY_2018_2_OR_NEWER
  254. var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
  255. #else
  256. var prefab = PrefabUtility.GetPrefabParent(go);
  257. #endif
  258. if (!prefab) {
  259. Debug.LogError("Prefab asset not valid!");
  260. return;
  261. }
  262. #if UNITY_2018_3_OR_NEWER
  263. if (PrefabUtility.GetPrefabInstanceStatus(prefab) == PrefabInstanceStatus.Connected)
  264. PrefabUtility.ApplyPrefabInstance(prefab, InteractionMode.UserAction);
  265. 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"))
  266. PrefabUtility.RevertPrefabInstance(prefab, InteractionMode.UserAction);
  267. EditorUtility.SetDirty(prefab);
  268. #else
  269. var selection = Selection.instanceIDs;
  270. Selection.activeGameObject = go;
  271. if (EditorApplication.ExecuteMenuItem("GameObject/Apply Changes To Prefab")) {
  272. EditorUtility.SetDirty(prefab);
  273. Selection.instanceIDs = selection;
  274. } else
  275. Debug.LogError("Failed to apply prefab modifications");
  276. #endif
  277. } else if (allowCreatingNew) {
  278. var path = EditorUtility.SaveFilePanelInProject("Save prefab", "New Prefab", "prefab", "Save the selected prefab");
  279. if (!string.IsNullOrEmpty(path))
  280. #if UNITY_2018_3_OR_NEWER
  281. PrefabUtility.SaveAsPrefabAssetAndConnect(go, path, InteractionMode.UserAction);
  282. #else
  283. PrefabUtility.CreatePrefab(path, go, ReplacePrefabOptions.ConnectToPrefab);
  284. #endif
  285. }
  286. }
  287. public static string EnumFlagsToString(Enum value) {
  288. using(ProfilerSample.Get())
  289. try {
  290. if ((int)(object)value == -1)
  291. return "Everything";
  292. var str = new StringBuilder();
  293. var separator = ", ";
  294. foreach (var enumValue in Enum.GetValues(value.GetType())) {
  295. var i = (int)enumValue;
  296. if (i != 0 && (i & (i - 1)) == 0 && Enum.IsDefined(value.GetType(), i) && (Convert.ToInt32(value) & i) != 0) {
  297. str.Append(ObjectNames.NicifyVariableName(enumValue.ToString()));
  298. str.Append(separator);
  299. }
  300. }
  301. if (str.Length > 0)
  302. str.Length -= separator.Length;
  303. return str.ToString();
  304. } catch (Exception e) {
  305. if (Preferences.DebugEnabled)
  306. Debug.LogException(e);
  307. return string.Empty;
  308. }
  309. }
  310. public static GUIContent GetTempGUIContent(string text, string tooltip = null, Texture2D image = null) {
  311. tempContent.text = text;
  312. tempContent.tooltip = tooltip;
  313. tempContent.image = image;
  314. return tempContent;
  315. }
  316. public static string SafeGetName(this IconBase icon) {
  317. try {
  318. return icon.Name;
  319. } catch (Exception e) {
  320. Debug.LogException(e);
  321. Preferences.ForceDisableButton(icon);
  322. return string.Empty;
  323. }
  324. }
  325. public static float SafeGetWidth(this IconBase icon) {
  326. try {
  327. return icon.Width + (Preferences.IconsSize - 15) / 2;
  328. } catch (Exception e) {
  329. Debug.LogException(e);
  330. Preferences.ForceDisableButton(icon);
  331. return 0f;
  332. }
  333. }
  334. public static void SafeInit(this IconBase icon) {
  335. try {
  336. icon.Init();
  337. } catch (Exception e) {
  338. Debug.LogException(e);
  339. Preferences.ForceDisableButton(icon);
  340. }
  341. }
  342. public static void SafeDoGUI(this IconBase icon, Rect rect) {
  343. try {
  344. rect.yMin -= (Preferences.IconsSize - 15) / 2;
  345. rect.xMin -= (Preferences.IconsSize - 15) / 2;
  346. icon.DoGUI(rect);
  347. } catch (Exception e) {
  348. Debug.LogException(e);
  349. Preferences.ForceDisableButton(icon);
  350. }
  351. }
  352. public static Rect FlipRectHorizontally(Rect rect) {
  353. return Rect.MinMaxRect(
  354. rect.xMax,
  355. rect.yMin,
  356. rect.xMin,
  357. rect.yMax
  358. );
  359. }
  360. }
  361. }