├── EditorUtil.cs.meta ├── EditorViewModules.cs.meta ├── Modules ├── KillNormalMapFix.cs.meta ├── MainToolbarsView.cs.meta ├── SceneToolsModule.cs.meta ├── PlayModeButtonsModule.cs.meta ├── KillNormalMapFix.cs ├── SceneToolsModule.cs ├── PlayModeButtonsModule.cs └── MainToolbarsView.cs ├── README.md ├── LICENSE ├── .gitignore ├── EditorUtil.cs └── EditorViewModules.cs /EditorUtil.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 971af09ad4d2f03448e00932ad951bf6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /EditorViewModules.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 793b947cb2a374849b1b90498c56b906 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Modules/KillNormalMapFix.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d829a09ac5a3f0d4d82079fa7603995a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Modules/MainToolbarsView.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 413d1b3a775a37c4eb8b7579bc63bbb9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Modules/SceneToolsModule.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e138d33009a6f95429035a61eeee44ee 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Modules/PlayModeButtonsModule.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2278d73d542a3224c92b2211d3eb30ae 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Modules/KillNormalMapFix.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace AV.Toolkit 9 | { 10 | public class KillNormalMapFix : EditorViewModule 11 | { 12 | public override IEnumerable GetTargetTypes() 13 | { 14 | yield return typeof(Editor).Assembly.GetType("UnityEditor.BumpMapSettingsFixingWindow"); 15 | } 16 | 17 | public override void OnViewRefresh() 18 | { 19 | view.window.Close(); 20 | } 21 | } 22 | } 23 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Editor-View-Modules 2 | Customize Toolbars and any EditorWindow with contextual modules.\ 3 | Hide MainToolbar and StatusBar, works best together with [Borderless Unity](https://github.com/neon-age/Borderless-Unity) 4 | 5 | [![twitter](https://img.shields.io/twitter/follow/_neonage?style=social)](https://twitter.com/_neonage)\ 6 | [![discord online](https://img.shields.io/discord/830405926078644254?label=Open%20Labs&logo=discord&style=social)](https://discord.gg/uF3sJFMA2j) 7 | 8 | https://user-images.githubusercontent.com/29812914/168375796-8dc93354-62b7-415d-8363-ec173c76d917.mp4 9 | 10 | ## Open Labs Community 11 | [![join discord](https://user-images.githubusercontent.com/29812914/121816656-0cb93080-cca7-11eb-954a-344cfd31f530.png)](https://discord.gg/uF3sJFMA2j) 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Neonage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /EditorUtil.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | using UnityEditor; 6 | using UnityEditor.ShortcutManagement; 7 | using UnityEngine; 8 | using Object = UnityEngine.Object; 9 | 10 | namespace AV.Toolkit 11 | { 12 | public static class EditorUtil 13 | { 14 | const BindingFlags AnyBind = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; 15 | 16 | public static Assembly Asm = typeof(Editor).Assembly; 17 | 18 | static FieldInfo globalEventHandlerInfo = typeof(EditorApplication).GetField("globalEventHandler", AnyBind); 19 | 20 | public static void SetGlobalKeyHandler(EditorApplication.CallbackFunction func, bool add) 21 | { 22 | var value = (EditorApplication.CallbackFunction)globalEventHandlerInfo.GetValue(null); 23 | if (add) value += func; else value -= func; 24 | globalEventHandlerInfo.SetValue(null, value); 25 | } 26 | 27 | 28 | public static void AddOrAssign(Dictionary lookup, K k, V v) 29 | { 30 | if (!lookup.ContainsKey(k)) lookup.Add(k, v); else lookup[k] = v; 31 | } 32 | public static V GetOrAdd(Dictionary lookup, K k, Func createValue) 33 | { 34 | if (!lookup.TryGetValue(k, out var v)) v = createValue(); return v; 35 | } 36 | 37 | public static T FindObject() where T : Object 38 | { 39 | return FindObject(typeof(T)) as T; 40 | } 41 | public static Object FindObject(Type type) 42 | { 43 | var objs = Resources.FindObjectsOfTypeAll(type); 44 | return objs.Length > 0 ? objs[0] : null; 45 | } 46 | 47 | public static void Log(params object[] objs) 48 | { 49 | var l = ""; 50 | foreach (var o in objs) 51 | l += o.ToString() + " "; 52 | Debug.Log(l); 53 | } 54 | 55 | public static void TryInvoke(Action action) 56 | { 57 | try { action?.Invoke(); } catch (Exception ex) { Debug.LogException(ex); } 58 | } 59 | public static void TryInvoke(Action action, T1 v1) 60 | { 61 | try { action?.Invoke(v1); } catch (Exception ex) { Debug.LogException(ex); } 62 | } 63 | 64 | public static void TryInvoke(Func action) 65 | { 66 | try { action?.Invoke(); } catch (Exception ex) { Debug.LogException(ex); } 67 | } 68 | public static void TryInvoke(Func action, T1 v1) 69 | { 70 | try { action?.Invoke(v1); } catch (Exception ex) { Debug.LogException(ex); } 71 | } 72 | } 73 | } 74 | 75 | #endif -------------------------------------------------------------------------------- /Modules/SceneToolsModule.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | using UnityEditor; 8 | using UnityEngine; 9 | using UnityEngine.UIElements; 10 | 11 | namespace AV.Toolkit 12 | { 13 | class SceneToolsModule : EditorViewModule 14 | { 15 | const BindingFlags AnyBind = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; 16 | 17 | static Type editorToolGUIType = typeof(Editor).Assembly.GetType("UnityEditor.EditorToolGUI"); 18 | static MethodInfo getToolbarEntryRect = editorToolGUIType.GetMethod("GetToolbarEntryRect", AnyBind); 19 | static MethodInfo doBuiltinToolbar = editorToolGUIType.GetMethod("DoBuiltinToolbar", AnyBind); 20 | static MethodInfo doBuiltinToolSettings = editorToolGUIType.GetMethod("DoBuiltinToolSettings", AnyBind, null, new[] { typeof(Rect) }, null); 21 | static PropertyInfo activeToolSupportsGridSnap = typeof(EditorSnapSettings).GetProperty("activeToolSupportsGridSnap", AnyBind); 22 | 23 | 24 | public override IEnumerable GetTargetTypes() { yield return typeof(SceneView); } 25 | 26 | public override void OnViewRefresh() 27 | { 28 | if (MainToolbarsView.isMainToolbarEnabled) return; 29 | if (SceneView.lastActiveSceneView != view.window) return; 30 | var root = view.root; 31 | 32 | var param = new object[1]; 33 | var rect = new Rect(0, 0, 230 + 140 + 30, 18); 34 | 35 | var guiContainer = new IMGUIContainer(() => 36 | { 37 | var r = new Rect(rect) { y = -2, width = 224 }; 38 | param[0] = r; 39 | 40 | doBuiltinToolbar.Invoke(null, param); 41 | 42 | r.x += r.width; r.width = 136; r.y = -2; r.height += 4; 43 | param[0] = r; 44 | 45 | doBuiltinToolSettings.Invoke(null, param); 46 | 47 | r.x += r.width; r.width = 32; // r.y = -2; 48 | //using (new EditorGUI.DisabledScope(!(bool)activeToolSupportsGridSnap.GetValue(null))) 49 | { 50 | var snap = EditorSnapSettings.gridSnapEnabled; 51 | var icon = snap ? S.snapToGridIcons[1] : S.snapToGridIcons[0]; 52 | EditorSnapSettings.gridSnapEnabled = GUI.Toggle(r, snap, icon, S.command); 53 | } 54 | }) 55 | { style = { width = rect.width, height = rect.height, overflow = Overflow.Hidden } }; 56 | 57 | view.tabDock.Add(guiContainer); 58 | } 59 | 60 | static class S 61 | { 62 | public static readonly GUIStyle command = "AppCommand"; 63 | public static GUIContent[] snapToGridIcons = new GUIContent[] 64 | { 65 | EditorGUIUtility.TrIconContent("SceneViewSnap-Off", "Toggle Grid Snapping on and off. Available when you set tool handle rotation to Global."), 66 | EditorGUIUtility.TrIconContent("SceneViewSnap-On", "Toggle Grid Snapping on and off. Available when you set tool handle rotation to Global.") 67 | }; 68 | } 69 | } 70 | } 71 | 72 | #endif -------------------------------------------------------------------------------- /Modules/PlayModeButtonsModule.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | using UnityEditor; 9 | using UnityEngine; 10 | using UnityEngine.UIElements; 11 | 12 | namespace AV.Toolkit 13 | { 14 | class PlayModeButtonsModule : EditorViewModule 15 | { 16 | const BindingFlags AnyBind = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; 17 | 18 | static Type gameViewType = EditorUtil.Asm.GetType("UnityEditor.GameView"); 19 | static Type editorToolGUIType = EditorUtil.Asm.GetType("UnityEditor.EditorToolGUI"); 20 | 21 | public override IEnumerable GetTargetTypes() => new Type[] { gameViewType, typeof(SceneView) }; 22 | 23 | public override void OnViewRefresh() 24 | { 25 | if (MainToolbarsView.isMainToolbarEnabled) return; 26 | var gameVisible = view.IsAnyWindowVisible(gameViewType); 27 | if (view.type == typeof(SceneView) && gameVisible) 28 | return; 29 | if (!gameVisible && SceneView.lastActiveSceneView != view.window) 30 | return; 31 | 32 | if (view.type == gameViewType) 33 | { 34 | var gameViews = view.GetVisibleViews(gameViewType).ToArray(); 35 | if (gameViews.Length > 1) 36 | if (view != gameViews[0]) 37 | return; 38 | } 39 | 40 | var root = view.root; 41 | var rect = new Rect(0, 0, 128, 18); 42 | 43 | var guiContainer = new IMGUIContainer(DrawPlayModeButtons) 44 | { style = { width = rect.width, height = rect.height, overflow = Overflow.Hidden } }; 45 | 46 | view.tabDock.Add(guiContainer); 47 | } 48 | 49 | static void DrawPlayModeButtons() 50 | { 51 | GUILayout.BeginHorizontal(); 52 | 53 | var c = GUI.color + new Color(0.01f, 0.01f, 0.01f, 0.01f); 54 | var contentColor = new Color(1.0f / c.r, 1.0f / c.g, 1.0f / c.g, 1.0f / c.a); 55 | GUI.contentColor = contentColor; 56 | GUI.backgroundColor = Color.white; 57 | 58 | var isPlaying = EditorApplication.isPlaying; 59 | if (isPlaying) 60 | GUI.backgroundColor = (Color)new Color32(35, 74, 108, 255) * 4; 61 | 62 | isPlaying = GUILayout.Toggle(isPlaying, S.playIcon, S.barButton); 63 | if (GUI.changed) 64 | EditorApplication.isPlaying = isPlaying; 65 | GUI.backgroundColor = Color.white; 66 | 67 | var isPaused = GUILayout.Toggle(EditorApplication.isPaused, S.pauseIcon, S.barButton); 68 | if (GUI.changed) 69 | EditorApplication.isPaused = isPaused; 70 | 71 | if (GUILayout.Button(S.stepIcon, S.barButton)) 72 | EditorApplication.Step(); 73 | 74 | GUILayout.Space(10); 75 | GUILayout.EndHorizontal(); 76 | } 77 | 78 | 79 | static class S 80 | { 81 | public static readonly GUIContent playIcon = EditorGUIUtility.TrIconContent("PlayButton"); 82 | public static readonly GUIContent pauseIcon = EditorGUIUtility.TrIconContent("PauseButton"); 83 | public static readonly GUIContent stepIcon = EditorGUIUtility.TrIconContent("StepButton"); 84 | 85 | public static readonly GUIStyle barButton = new GUIStyle(EditorStyles.toolbarButton) { fixedHeight = 18, padding = new RectOffset(8, 8, 0, 0) }; 86 | public static readonly GUIStyle command = "AppCommand"; 87 | 88 | public static GUIContent[] snapToGridIcons = new GUIContent[] 89 | { 90 | EditorGUIUtility.TrIconContent("SceneViewSnap-Off", "Toggle Grid Snapping on and off. Available when you set tool handle rotation to Global."), 91 | EditorGUIUtility.TrIconContent("SceneViewSnap-On", "Toggle Grid Snapping on and off. Available when you set tool handle rotation to Global.") 92 | }; 93 | } 94 | } 95 | } 96 | 97 | #endif -------------------------------------------------------------------------------- /Modules/MainToolbarsView.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEngine; 3 | using UnityEditor; 4 | using System.Reflection; 5 | using System; 6 | using Object = UnityEngine.Object; 7 | 8 | namespace AV.Toolkit 9 | { 10 | public static class MainToolbarsView 11 | { 12 | const BindingFlags AnyBind = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; 13 | 14 | static Type mainViewT = EditorUtil.Asm.GetType("UnityEditor.MainView"); 15 | static Type viewT = mainViewT.BaseType; 16 | static Type appBarT = EditorUtil.Asm.GetType("UnityEditor.AppStatusBar"); 17 | static Type toolbarT = EditorUtil.Asm.GetType("UnityEditor.Toolbar"); 18 | static FieldInfo m_UseTopView = mainViewT.GetField("m_UseTopView", AnyBind); 19 | static FieldInfo m_TopViewHeight = mainViewT.GetField("m_TopViewHeight", AnyBind); 20 | static FieldInfo m_UseBottomView = mainViewT.GetField("m_UseBottomView", AnyBind); 21 | static MethodInfo addChild = mainViewT.GetMethod("AddChild", AnyBind, null, new Type[] { viewT }, null); 22 | static MethodInfo insertChild = mainViewT.GetMethod("AddChild", AnyBind, null, new Type[] { viewT, typeof(int) }, null); 23 | static MethodInfo removeChild = mainViewT.GetMethod("RemoveChild", AnyBind, null, new Type[] { viewT }, null); 24 | static MethodInfo SetPosition = mainViewT.GetMethod("SetPosition", AnyBind); 25 | static PropertyInfo windowPosition = mainViewT.GetProperty("windowPosition", AnyBind); 26 | static PropertyInfo position = viewT.GetProperty("position", AnyBind); 27 | 28 | const string ToggleMainToolbarPath = "View/Toolbars/Main Toolbar"; 29 | const string ToggleAppStatusBarPath = "View/Toolbars/Status Bar"; 30 | 31 | public static bool isMainToolbarEnabled => Resources.FindObjectsOfTypeAll(toolbarT).Length != 0; 32 | public static bool isAppStatusBarEnabled => Resources.FindObjectsOfTypeAll(appBarT).Length != 0; 33 | 34 | [MenuItem(ToggleMainToolbarPath, true)] 35 | static bool MainToolbarValidate() { Menu.SetChecked(ToggleMainToolbarPath, isMainToolbarEnabled); return true; } 36 | [MenuItem(ToggleAppStatusBarPath, true)] 37 | static bool AppStatusBarValidate() { Menu.SetChecked(ToggleAppStatusBarPath, isAppStatusBarEnabled); return true; } 38 | 39 | [MenuItem(ToggleMainToolbarPath)] 40 | public static void ToggleMainToolbar() 41 | { 42 | EditorApplication.delayCall += EditorViewModule.RefreshAllModules; 43 | var exist = Resources.FindObjectsOfTypeAll(toolbarT).Length != 0; 44 | var mainView = Resources.FindObjectsOfTypeAll(mainViewT)[0]; 45 | 46 | if (!exist) 47 | { 48 | var toolbar = ScriptableObject.CreateInstance(toolbarT); 49 | 50 | m_TopViewHeight.SetValue(mainView, 30); 51 | m_UseTopView.SetValue(mainView, true); 52 | 53 | insertChild.Invoke(mainView, new object[] { toolbar, 0 }); 54 | UpdateMainViewPosition(mainView); 55 | return; 56 | } 57 | var bar = Resources.FindObjectsOfTypeAll(toolbarT)[0]; 58 | m_TopViewHeight.SetValue(mainView, -1); 59 | m_UseTopView.SetValue(mainView, false); 60 | 61 | removeChild.Invoke(mainView, new object[] { bar }); 62 | 63 | var pos = (Rect)windowPosition.GetValue(mainView); 64 | SetPosition.Invoke(mainView, new object[] { pos }); 65 | 66 | Object.DestroyImmediate(bar); 67 | } 68 | 69 | [MenuItem(ToggleAppStatusBarPath)] 70 | public static void ToggleAppStatusBar() 71 | { 72 | EditorApplication.delayCall += EditorViewModule.RefreshAllModules; 73 | var exist = Resources.FindObjectsOfTypeAll(appBarT).Length != 0; 74 | var mainView = Resources.FindObjectsOfTypeAll(mainViewT)[0]; 75 | 76 | if (!exist) 77 | { 78 | var toolbar = ScriptableObject.CreateInstance(appBarT); 79 | 80 | m_UseBottomView.SetValue(mainView, true); 81 | 82 | addChild.Invoke(mainView, new object[] { toolbar }); 83 | var pos = (Rect)position.GetValue(toolbar); 84 | pos.height = 20; 85 | position.SetValue(toolbar, pos); 86 | UpdateMainViewPosition(mainView); 87 | EditorViewModule.RefreshAllModules(); 88 | return; 89 | } 90 | var bar = Resources.FindObjectsOfTypeAll(appBarT)[0]; 91 | m_UseBottomView.SetValue(mainView, false); 92 | 93 | removeChild.Invoke(mainView, new object[] { bar }); 94 | 95 | UpdateMainViewPosition(mainView); 96 | 97 | Object.DestroyImmediate(bar); 98 | } 99 | 100 | static void UpdateMainViewPosition(object mainView) 101 | { 102 | var pos = (Rect)windowPosition.GetValue(mainView); 103 | SetPosition.Invoke(mainView, new object[] { pos }); 104 | } 105 | } 106 | } 107 | #endif -------------------------------------------------------------------------------- /EditorViewModules.cs: -------------------------------------------------------------------------------- 1 | 2 | #if UNITY_EDITOR 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using UnityEngine.UIElements; 10 | using Ex = System.Exception; 11 | 12 | namespace AV.Toolkit 13 | { 14 | public abstract class EditorViewModule 15 | { 16 | static EditorViewModule activeModule; 17 | 18 | public virtual int priority { get; } 19 | public View view { get; private set; } // active view 20 | 21 | EditorViewModule SetView(View v) { view = v; return this; } 22 | 23 | IEnumerable targetTypes; 24 | 25 | public abstract IEnumerable GetTargetTypes(); 26 | public abstract void OnViewRefresh(); 27 | public virtual void OnRegister() {} 28 | //public virtual void OnFocusChange(bool focused) {} 29 | //public virtual void OnVisibilityChange(bool visible) {} 30 | 31 | static Dictionary views = new Dictionary(); 32 | static List modules = new List(); 33 | static bool wasMaximized; 34 | 35 | static void LogEx(Ex ex) => Debug.LogException(ex); 36 | 37 | [InitializeOnLoadMethod] 38 | static void Init() 39 | { 40 | views.Clear(); 41 | Action onActualViewChanged = OnActualViewChanged; 42 | R.actualViewChangedInfo.AddMethod.Invoke(null, new object[] { onActualViewChanged }); 43 | 44 | EditorApplication.update += OnEditorUpdate; 45 | EditorApplication.delayCall += RefreshAllModules; 46 | EditorApplication.playModeStateChanged += OnPlayModeChange; 47 | 48 | foreach (var moduleType in TypeCache.GetTypesDerivedFrom()) 49 | { 50 | try { CreateViewModule(moduleType); } catch (Ex ex) { LogEx(ex); } 51 | } 52 | modules.Sort((x, y) => x.priority.CompareTo(y.priority)); 53 | } 54 | static void CreateViewModule(Type moduleType) 55 | { 56 | var m = (EditorViewModule)Activator.CreateInstance(moduleType); 57 | m.OnRegister(); 58 | m.targetTypes = m.GetTargetTypes(); 59 | modules.Add(m); 60 | } 61 | 62 | 63 | static EditorWindow focusedWindow; 64 | static void OnEditorUpdate() 65 | { 66 | if (focusedWindow != EditorWindow.focusedWindow) 67 | { 68 | var last = focusedWindow; 69 | focusedWindow = EditorWindow.focusedWindow; 70 | var focus = last != focusedWindow; 71 | 72 | TryInitView(last, out var a); TryInitView(focusedWindow, out var b); 73 | 74 | //if (a != null) InvokeViewEvents(a, m => m.OnFocusChange(focus)); 75 | //if (b != null) InvokeViewEvents(b, m => m.OnFocusChange(!focus)); 76 | 77 | RefreshAllModules(); 78 | } 79 | } 80 | 81 | static void OnActualViewChanged(object hostView) 82 | { 83 | var w = R.actualView.GetValue(hostView) as EditorWindow; 84 | if (!w) 85 | return; 86 | 87 | if (TryInitView(w, out var v)) InvokeViewEvents(v, m => m.OnViewRefresh()); 88 | 89 | // Re-init all views, so multiple modules that depend on each other (like SceneTools and PlayModeButtons) get proper rebuild 90 | RefreshAllModules(); 91 | } 92 | 93 | static void OnPlayModeChange(PlayModeStateChange state) 94 | { 95 | //if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.EnteredPlayMode) 96 | // RefreshAllModules(); 97 | } 98 | 99 | public static void RefreshAllModules() 100 | { 101 | foreach (var w in Resources.FindObjectsOfTypeAll()) 102 | if (TryInitView(w, out var v)) 103 | InvokeViewEvents(v, m => m.OnViewRefresh()); 104 | } 105 | 106 | static void InvokeViewEvents(View v, Action action) 107 | { 108 | foreach (var m in GetAssignableModules(v)) 109 | try { m.view = v; action(m); } catch (Ex ex) { LogEx(ex); } 110 | } 111 | 112 | static IEnumerable GetAssignableModules(View v) 113 | { 114 | foreach (var m in modules) 115 | if (IsAssignable(v.type, m.targetTypes)) 116 | yield return m; 117 | } 118 | 119 | static bool IsAssignable(Type type, IEnumerable types) 120 | { 121 | foreach (var t in types) if (t.IsAssignableFrom(type)) { return true; } 122 | return false; 123 | } 124 | 125 | static bool TryInitView(EditorWindow w, out View view) 126 | { 127 | view = null; 128 | if (!w) return false; 129 | var root = w.rootVisualElement; 130 | if (root == null) return false; 131 | if (root.parent == null) return false; 132 | 133 | var id = w.GetInstanceID(); 134 | if (!views.TryGetValue(id, out view)) 135 | views.Add(id, view = new View()); 136 | 137 | view.Init(w); 138 | return true; 139 | } 140 | 141 | 142 | public enum ShowMode { NormalWindow, PopupMenu, Utility, NoShadow, MainWindow, AuxWindow, Tooltip, ModalUtility } 143 | 144 | /// Per-window data 145 | public class View : IEquatable 146 | { 147 | public object containerWindow; 148 | public object hostView; 149 | public bool isMaximized; 150 | public bool isFloating => showMode != ShowMode.MainWindow; 151 | public ShowMode showMode; 152 | public int id; 153 | public Type type; 154 | public EditorWindow window; 155 | public VisualElement root; 156 | public VisualElement userRoot; 157 | public VisualElement tabDock; 158 | public Action onGUI; 159 | IMGUIContainer guiArea; 160 | 161 | public Event evt = new Event(); 162 | 163 | bool isDockArea; 164 | bool isVisible; 165 | 166 | public bool Equals(View other) => id == other.id; 167 | 168 | public IEnumerable GetVisibleViews(Type windowType) 169 | { 170 | foreach (var v in views.Values) 171 | if (windowType.IsAssignableFrom(v.type)) 172 | if (v.isVisible) yield return v; 173 | } 174 | public bool IsAnyWindowVisible(Type windowType) => GetVisibleViews(windowType).Count() > 0; 175 | 176 | public void Clear() 177 | { 178 | tabDock?.Clear(); 179 | userRoot.Clear(); 180 | onGUI = null; 181 | guiArea.onGUIHandler -= OnGUIHandler; 182 | } 183 | 184 | internal void OnBecameVisible() 185 | { 186 | isVisible = true; 187 | guiArea.onGUIHandler += OnGUIHandler; 188 | InvokeVisibilityChange(true); 189 | } 190 | void OnBecameInvisible() 191 | { 192 | isVisible = false; 193 | guiArea.onGUIHandler -= OnGUIHandler; 194 | InvokeVisibilityChange(false); 195 | } 196 | 197 | void InvokeVisibilityChange(bool visible) 198 | { 199 | //foreach (var m in GetAssignableModules(this)) 200 | // try { m.view = this; m.OnVisibilityChange(visible); } catch (Ex ex) { LogEx(ex); } 201 | } 202 | 203 | void OnGUIHandler() 204 | { 205 | try { 206 | if (tabDock != null) 207 | UpdateTabDockRect(); 208 | onGUI?.Invoke(); 209 | } 210 | catch(Exception ex) { Debug.LogException(ex); } 211 | } 212 | 213 | internal void Init(EditorWindow window) 214 | { 215 | this.id = window.GetInstanceID(); 216 | this.type = window.GetType(); 217 | this.window = window; 218 | this.hostView = R.m_Parent.GetValue(window); 219 | this.containerWindow = R.m_Window.GetValue(hostView); 220 | this.showMode = 0; 221 | if (containerWindow != null) 222 | this.showMode = (ShowMode)R.windowShowMode.GetValue(containerWindow); 223 | 224 | this.root = window.rootVisualElement.parent; 225 | this.userRoot = root.Q(name: "user-root-element"); 226 | this.tabDock = root.Q(name: "view-tab-dock"); 227 | this.guiArea = root.Q(); 228 | 229 | var onBecameInvisible = (Delegate)R.m_OnBecameInvisible.GetValue(hostView); 230 | onBecameInvisible = Delegate.Combine(onBecameInvisible, R.ConvertDelegate(OnBecameInvisible, R.windowDelegateT)); 231 | R.m_OnBecameInvisible.SetValue(hostView, onBecameInvisible); 232 | 233 | isMaximized = hostView.GetType() == R.maximizedHostT; 234 | isDockArea = hostView.GetType() == R.dockAreaT; 235 | 236 | if (tabDock == null) 237 | root.Add(tabDock = new VisualElement() { pickingMode = PickingMode.Ignore, name = "view-tab-dock" }); 238 | 239 | if (userRoot == null) 240 | root.Add(userRoot = new VisualElement() { pickingMode = PickingMode.Ignore, name = "user-root-element" }); 241 | 242 | if (tabDock != null) 243 | tabDock.style.flexDirection = FlexDirection.Row; 244 | 245 | userRoot.StretchToParentSize(); 246 | 247 | Clear(); 248 | OnBecameVisible(); 249 | } 250 | 251 | static GUIStyle titleLabel; 252 | 253 | void UpdateTabDockRect() 254 | { 255 | var winPos = window.position; 256 | var tabDockRect = new Rect(winPos) { x = 0, y = 0, height = 21 }; 257 | 258 | if (isFloating) tabDockRect.y += 2; 259 | 260 | if (isMaximized) 261 | { 262 | if (titleLabel == null) 263 | titleLabel = "dragtab"; 264 | 265 | var titleContent = window.titleContent; 266 | 267 | tabDockRect.xMin += 16 + titleLabel.CalcSize(titleContent).x; 268 | } 269 | else if (isDockArea) 270 | { 271 | var totalTabWidth = (float)R.m_TotalTabWidth.GetValue(hostView); 272 | tabDockRect.xMin += totalTabWidth; 273 | } 274 | else 275 | { 276 | R.veLayout.SetValue(tabDock, tabDockRect); 277 | return; 278 | } 279 | tabDockRect.xMax -= (float)R.GetExtraButtonsWidth.Invoke(hostView, null) + 24f; 280 | tabDockRect.width = Mathf.Max(0, tabDockRect.width); 281 | 282 | R.veLayout.SetValue(tabDock, tabDockRect); 283 | } 284 | } 285 | 286 | static class R 287 | { 288 | public const BindingFlags AnyBind = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; 289 | 290 | public static readonly Type viewT = EditorUtil.Asm.GetType("UnityEditor.View"); 291 | public static readonly Type hostViewT = EditorUtil.Asm.GetType("UnityEditor.HostView"); 292 | public static readonly Type dockAreaT = EditorUtil.Asm.GetType("UnityEditor.DockArea"); 293 | public static readonly Type maximizedHostT = EditorUtil.Asm.GetType("UnityEditor.MaximizedHostView"); 294 | public static readonly Type containerWindowT = EditorUtil.Asm.GetType("UnityEditor.ContainerWindow"); 295 | public static readonly Type windowDelegateT = hostViewT.GetNestedType("EditorWindowDelegate", AnyBind); 296 | 297 | public static readonly FieldInfo m_Pos = typeof(EditorWindow).GetField("m_Pos", AnyBind); 298 | public static readonly FieldInfo m_Position = viewT.GetField("m_Position", AnyBind); 299 | public static readonly FieldInfo m_Parent = typeof(EditorWindow).GetField("m_Parent", AnyBind); 300 | public static readonly FieldInfo m_BorderSize = hostViewT.GetField("m_BorderSize", AnyBind); 301 | public static readonly FieldInfo m_TotalTabWidth = dockAreaT.GetField("m_TotalTabWidth", AnyBind); 302 | public static readonly FieldInfo windowShowMode = containerWindowT.GetField("m_ShowMode", AnyBind); 303 | public static readonly FieldInfo m_OnBecameInvisible = hostViewT.GetField("m_OnBecameInvisible", AnyBind); 304 | public static readonly FieldInfo m_OnGUI = hostViewT.GetField("m_OnGUI", AnyBind); 305 | public static readonly FieldInfo oldOnGUI = hostViewT.GetField("m_OnGUI", AnyBind); 306 | 307 | public static readonly EventInfo actualViewChangedInfo = hostViewT.GetEvent("actualViewChanged", AnyBind); 308 | public static readonly MethodInfo GetExtraButtonsWidth = hostViewT.GetMethod("GetExtraButtonsWidth", AnyBind); 309 | public static readonly MethodInfo CreateDelegate = hostViewT.GetMethod("CreateDelegate", AnyBind); 310 | public static readonly MethodInfo evtCopyFrom = typeof(Event).GetMethod("CopyFrom", AnyBind); 311 | 312 | public static readonly PropertyInfo m_Window = viewT.GetProperty("window", AnyBind); 313 | public static readonly PropertyInfo windowPosition = viewT.GetProperty("windowPosition", AnyBind); 314 | public static readonly PropertyInfo actualView = hostViewT.GetProperty("actualView", AnyBind); 315 | public static readonly PropertyInfo veLayout = typeof(VisualElement).GetProperty("layout", AnyBind); 316 | 317 | public static readonly FieldInfo genericMenuTopOffset = 318 | dockAreaT.GetNestedType("Styles", AnyBind).GetField("genericMenuTopOffset", AnyBind); 319 | public static readonly FieldInfo svcFloatValue = genericMenuTopOffset.FieldType.GetField("m_Value", AnyBind); 320 | 321 | 322 | public static Delegate ConvertDelegate(Action src, Type type) => ConvertDelegate((Delegate)src, type); 323 | public static Delegate ConvertDelegate(Delegate src, Type type) 324 | { 325 | return Delegate.CreateDelegate(type, src.Target, src.Method); 326 | } 327 | public static Delegate WindowDelegate(Action action) => ConvertDelegate(action, windowDelegateT); 328 | 329 | static object[] objParam = new object[1]; 330 | static object[] objParam2 = new object[2]; 331 | public static object[] ObjParam(object p1) { objParam[0] = p1; return objParam; } 332 | public static object[] ObjParam(object p1, object p2) { objParam2[0] = p1; objParam2[1] = p2; return objParam2; } 333 | } 334 | } 335 | } 336 | 337 | #endif 338 | --------------------------------------------------------------------------------