#if UNITY_EDITOR using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEditor.ShortcutManagement; using System.Reflection; using System.Linq; using UnityEngine.UIElements; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using System.Diagnostics; using Type = System.Type; using Delegate = System.Delegate; using Action = System.Action; using static VTabs.Libs.VUtils; using static VTabs.Libs.VGUI; namespace VTabs { public static class VTabs { static void UpdateGUIs() { foreach (var dockArea in allDockAreas) if (!guis_byDockArea.ContainsKey(dockArea)) guis_byDockArea[dockArea] = new VTabsGUI(dockArea); foreach (var dockArea in guis_byDockArea.Keys.ToList().Where(r => !r)) guis_byDockArea.Remove(dockArea); foreach (var gui in guis_byDockArea.Values) { gui.UpdateScrollAnimation(); gui.UpdateLockButtonHiding(); } // foreach (var dockArea in allDockAreas) // // dockArea.GetMemberValue("m_BorderSize").Log(r => dockArea.name + ": " + r.left); // dockArea.GetMemberValue("m_TabAreaRect").Log(r => dockArea.name + ": " + r.x); } public static Dictionary guis_byDockArea = new(); public static void UpdateStyleSheet() { if (!Application.unityVersion.StartsWith("6000")) return; void updatePluginFolderPath() { if (AssetDatabase.LoadAssetAtPath(pluginFolderPath.CombinePath("VTabs.cs")) is not null) return; var mainScriptPath = GetScriptPath(nameof(VTabs)); ProjectPrefs.SetString("vTabs-plugin-folder-path", mainScriptPath.GetParentPath()); } void generate() { var s = ""; void addComment() { s += @" /* This file is generated by vTabs to modify tab style */ /* Feel free to remove it from version control */ "; } void addLargeTabs() { if (!VTabsMenu.largeTabStyleEnabled) return; s += @" /* tab text */ .dragtab { padding-left: 10px; } /* tab itself */ .tab { padding-right: 27px; } "; } void addNeatTabs() { if (!VTabsMenu.neatTabStyleEnabled) return; s += @" /* tab text */ .dragtab { padding-left: 10px; } /* tab itself */ .tab { padding-right: -1px; } "; } void addClassicBackground_dark() { if (!VTabsMenu.classicBackgroundEnabled) return; if (!isDarkTheme) return; s += @" /* background */ .dockarea { background-color: #262626; } /* tab text */ .dragtab { color: #dedede; } /* top and bottom bars */ .AppToolbar { background-color: #181818; } "; } void addClassicBackground_light() { if (!VTabsMenu.classicBackgroundEnabled) return; if (isDarkTheme) return; s += @" /* background */ .dockarea { background-color: #a9a9a9; } /* top and bottom bars */ .AppToolbar { background-color: #888888; } "; } void addGreyBackground() { if (!VTabsMenu.greyBackgroundEnabled) return; s += @" /* background */ .dockarea { background-color: #222222; } /* tab text */ .dragtab { color: #dedede; } /* top and bottom bars */ .AppToolbar { background-color: #222222; } "; } void save() { styleSheetPath.EnsureDirExists(); System.IO.File.WriteAllText(styleSheetPath, s); AssetDatabase.ImportAsset(styleSheetPath); } void addMetadata() { var importer = AssetImporter.GetAtPath(styleSheetPath); importer.userData = VTabsMenu.tabStyle + " " + VTabsMenu.backgroundStyle + " " + (isDarkTheme ? 1 : 0); importer.Dirty(); importer.SaveAndReimport(); } addComment(); addLargeTabs(); addNeatTabs(); addClassicBackground_dark(); addClassicBackground_light(); addGreyBackground(); save(); addMetadata(); EditorUtility.RequestScriptReload(); } void delete() { System.IO.Directory.Delete(styleSheetsFolderPath, recursive: true); System.IO.File.Delete(styleSheetsFolderPath + ".meta"); AssetDatabase.Refresh(); } void update() { var importer = AssetImporter.GetAtPath(styleSheetPath); if (importer.userData == null || importer.userData.Length != 5) return; var tabStyle = (int)char.GetNumericValue(importer.userData[0]); var backgroundStyle = (int)char.GetNumericValue(importer.userData[2]); var wasDarkTheme = (int)char.GetNumericValue(importer.userData[4]) == 1; if (tabStyle != VTabsMenu.tabStyle || backgroundStyle != VTabsMenu.backgroundStyle || isDarkTheme != wasDarkTheme) generate(); } updatePluginFolderPath(); var hasStyleSheet = AssetDatabase.LoadAssetAtPath(styleSheetPath) != null; var shouldHaveStyleSheet = (VTabsMenu.tabStyle != 0 || VTabsMenu.backgroundStyle != 0) && !VTabsMenu.pluginDisabled; if (shouldHaveStyleSheet && !hasStyleSheet) generate(); if (!shouldHaveStyleSheet && hasStyleSheet) delete(); if (shouldHaveStyleSheet && hasStyleSheet) update(); } static string pluginFolderPath => ProjectPrefs.GetString("vTabs-plugin-folder-path"); static string styleSheetsFolderPath => pluginFolderPath.CombinePath("StyleSheets"); static string styleSheetPath => pluginFolderPath.CombinePath("StyleSheets/Extensions/common.uss"); static void Shortcuts() // globalEventHandler { if (!curEvent.isKeyDown) return; void addTab() { if (curEvent.keyCode != KeyCode.T) return; if (curEvent.modifiers != EventModifiers.Control && curEvent.modifiers != EventModifiers.Command) return; if (!VTabsMenu.addTabShortcutEnabled) return; if (EditorWindow.mouseOverWindow.GetDockArea() is not Object dockArea) return; if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) return; VTabsAddTabWindow.Open(dockArea); EditorWindow.mouseOverWindow.Repaint(); // for + button to light up curEvent.Use(); } void closeTab() { if (curEvent.keyCode != KeyCode.W) return; if (curEvent.modifiers != EventModifiers.Control && curEvent.modifiers != EventModifiers.Command) return; if (!VTabsMenu.closeTabShortcutEnabled) return; if (EditorWindow.mouseOverWindow.GetDockArea() is not Object dockArea) return; if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) return; if (gui.tabs.Count == 1) return; gui.CloseTab(gui.activeTab); curEvent.Use(); } void reopenTab() { if (curEvent.keyCode != KeyCode.T) return; if (curEvent.modifiers != (EventModifiers.Command | EventModifiers.Shift) && curEvent.modifiers != (EventModifiers.Control | EventModifiers.Shift)) return; if (!VTabsMenu.reopenTabShortcutEnabled) return; if (EditorWindow.mouseOverWindow.GetDockArea() is not Object dockArea) return; if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) return; gui.ReopenClosedTab(); curEvent.Use(); } addTab(); closeTab(); reopenTab(); } static void UpdateBrowserTitle(EditorWindow browser) { if (mi_VFavorites_CanBrowserBeWrapped != null && mi_VFavorites_CanBrowserBeWrapped.Invoke(null, new[] { browser }).Equals(false)) return; var isLocked = browser.GetMemberValue("isLocked"); var isTitleDefault = browser.titleContent.text == "Project"; void setLockedTitle() { if (!isLocked) return; var isOneColumn = browser.GetMemberValue("m_ViewMode") == 0; var path = isOneColumn ? browser.GetLockedFolderPath_oneColumn() : browser.InvokeMethod("GetActiveFolderPath"); var guid = path.ToGuid(); var name = path.GetFilename(); var icon = EditorGUIUtility.FindTexture("Project"); void getIconFromVFolders() { if (mi_VFolders_GetIcon == null) return; if (mi_VFolders_GetIcon.Invoke(null, new[] { guid }) is Texture2D iconFromVFolders) icon = iconFromVFolders; } getIconFromVFolders(); browser.titleContent = new GUIContent(name, icon); t_DockArea.GetFieldValue("s_GUIContents").Clear(); } void setDefaultTitle() { if (isLocked) return; if (isTitleDefault) return; var name = "Project"; var icon = EditorGUIUtility.FindTexture("Project@2x"); browser.titleContent = new GUIContent(name, icon); t_DockArea.GetFieldValue("s_GUIContents").Clear(); } setLockedTitle(); setDefaultTitle(); } static void UpdatePropertyEditorTitle(EditorWindow propertyEditor) { var obj = propertyEditor.GetMemberValue("m_InspectedObject"); if (!obj) return; var name = obj is Component component ? GetComponentName(component) : obj.name; var sourceIcon = AssetPreview.GetMiniThumbnail(obj); var adjustedIcon = sourceIcon; void getSourceIconFromVHierarchy() { if (mi_VHierarchy_GetIcon == null) return; if (obj is not GameObject gameObject) return; if (mi_VHierarchy_GetIcon.Invoke(null, new[] { gameObject }) is Texture2D iconFromVHierarchy) sourceIcon = iconFromVHierarchy; } void getAdjustedIcon() { if (adjustedObjectIconsBySourceIid.TryGetValue(sourceIcon.GetInstanceID(), out adjustedIcon)) return; adjustedIcon = new Texture2D(sourceIcon.width, sourceIcon.height, sourceIcon.format, sourceIcon.mipmapCount, false); adjustedIcon.hideFlags = HideFlags.DontSave; adjustedIcon.SetPropertyValue("pixelsPerPoint", (sourceIcon.width / 16f).RoundToInt()); Graphics.CopyTexture(sourceIcon, adjustedIcon); adjustedObjectIconsBySourceIid[sourceIcon.GetInstanceID()] = adjustedIcon; } getSourceIconFromVHierarchy(); getAdjustedIcon(); propertyEditor.titleContent = new GUIContent(name, adjustedIcon); propertyEditor.SetMemberValue("m_InspectedObject", null); // prevents further title updates from both internal code and vTabs t_DockArea.GetFieldValue("s_GUIContents").Clear(); } public static void UpdateTitle(EditorWindow window) { if (window == null) return; var isPropertyEditor = window.GetType() == t_PropertyEditor; var isBrowser = window.GetType() == t_ProjectBrowser; if (!isPropertyEditor && !isBrowser) return; if (isPropertyEditor) UpdatePropertyEditorTitle(window); if (isBrowser) if (window.GetPropertyValue("isLocked")) UpdateBrowserTitle(window); } static void UpdateAllBrowserTitles() { foreach (var r in allBrowsers) UpdateBrowserTitle(r); } static void UpdateAllPropertyEditorTitles() { foreach (var r in allPropertyEditors) UpdatePropertyEditorTitle(r); } static Dictionary adjustedObjectIconsBySourceIid = new Dictionary(); static void WrappedBrowserOnGUI(EditorWindow browser) { var headerHeight = 26; var footerHeight = 21; var breadcrubsYOffset = .5f; var headerRect = browser.position.SetPos(0, 0).SetHeight(headerHeight); var footerRect = browser.position.SetPos(0, 0).SetHeightFromBottom(footerHeight); var listAreaRect = browser.position.SetPos(0, 0).AddHeight(-footerHeight).AddHeightFromBottom(-headerHeight); var breadcrumbsRect = headerRect.AddHeightFromBottom(-breadcrubsYOffset * 2); var topGapRect = headerRect.SetHeight(breadcrubsYOffset * 2); var breadcrumbsTint = isDarkTheme ? Greyscale(0, .05f) : Greyscale(0, .02f); var topGapColor = isDarkTheme ? Greyscale(.24f, 1) : Greyscale(.8f, 1); var isOneColumn = browser.GetMemberValue("m_ViewMode") == 0; void setRootForOneColumn() { if (!isOneColumn) return; if (curEvent.isRepaint) return; if (browser.GetMemberValue("m_AssetTree") is not object m_AssetTree) return; if (m_AssetTree.GetMemberValue("data") is not object data) return; var m_rootInstanceID = data.GetMemberValue("m_rootInstanceID"); void setInitial() { if (m_rootInstanceID != 0) return; var folderPath = browser.GetLockedFolderPath_oneColumn(); var folderIid = AssetDatabase.LoadAssetAtPath(folderPath).GetInstanceID(); data.SetMemberValue("m_rootInstanceID", folderIid); m_AssetTree.InvokeMethod("ReloadData"); } void update() { if (m_rootInstanceID == 0) return; var folderIid = m_rootInstanceID; var folderPath = EditorUtility.InstanceIDToObject(folderIid).GetPath(); browser.SetLockedFolderPath_oneColumn(folderPath); browser.GetMemberValue("m_SearchFilter")?.SetMemberValue("m_Folders", new[] { folderPath }); // needed for breadcrumbs to display correctly } void reset() { if (browser.GetMemberValue("isLocked")) return; data.SetMemberValue("m_rootInstanceID", 0); browser.SetLockedFolderPath_oneColumn("Assets"); m_AssetTree.InvokeMethod("ReloadData"); // returns the browser to normal state on unlock } setInitial(); update(); reset(); } void handleFolderChange() { if (isOneColumn) return; void onBreadcrumbsClick() { if (!curEvent.isMouseUp) return; if (!breadcrumbsRect.IsHovered()) return; browser.RecordUndo(); toCallInGUI += () => UpdateBrowserTitle(browser); toCallInGUI += () => browser.Repaint(); } void onDoubleclick() { if (!curEvent.isMouseDown) return; if (curEvent.clickCount != 2) return; browser.RecordUndo(); EditorApplication.delayCall += () => UpdateBrowserTitle(browser); EditorApplication.delayCall += () => browser.Repaint(); } void onUndoRedo() { if (!curEvent.isKeyDown) return; if (!curEvent.holdingCmdOrCtrl) return; if (curEvent.keyCode != KeyCode.Z) return; var curFolderGuid = browser.InvokeMethod("GetActiveFolderPath").ToGuid(); EditorApplication.delayCall += () => { var delayedFolderGuid = browser.InvokeMethod("GetActiveFolderPath").ToGuid(); if (delayedFolderGuid == curFolderGuid) return; var folderIid = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(delayedFolderGuid)).GetInstanceID(); browser.InvokeMethod("SetFolderSelection", new[] { folderIid }, false); UpdateBrowserTitle(browser); }; } onBreadcrumbsClick(); onDoubleclick(); onUndoRedo(); } void oneColumn() { if (!isOneColumn) return; if (!browser.InvokeMethod("Initialized")) browser.InvokeMethod("Init"); var m_TreeViewKeyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard); browser.InvokeMethod("OnEvent"); if (curEvent.isMouseDown && browser.position.SetPos(0, 0).IsHovered()) t_ProjectBrowser.SetFieldValue("s_LastInteractedProjectBrowser", browser); // header browser.SetFieldValue("m_ListHeaderRect", breadcrumbsRect); if (curEvent.isRepaint) browser.InvokeMethod("BreadCrumbBar"); breadcrumbsRect.Draw(breadcrumbsTint); topGapRect.Draw(topGapColor); breadcrumbsRect.SetHeightFromBottom(1).Draw(Greyscale(.14f)); // footer browser.SetFieldValue("m_BottomBarRect", footerRect); browser.InvokeMethod("BottomBar"); // tree browser.GetMemberValue("m_AssetTree")?.InvokeMethod("OnGUI", listAreaRect, m_TreeViewKeyboardControlID); browser.InvokeMethod("HandleCommandEvents"); } void twoColumns() { if (isOneColumn) return; if (!browser.InvokeMethod("Initialized")) browser.InvokeMethod("Init"); var m_ListKeyboardControlID = GUIUtility.GetControlID(FocusType.Keyboard); var startGridSize = browser.GetFieldValue("m_ListArea")?.GetMemberValue("gridSize"); browser.InvokeMethod("OnEvent"); if (curEvent.isMouseDown && browser.position.SetPos(0, 0).IsHovered()) t_ProjectBrowser.SetFieldValue("s_LastInteractedProjectBrowser", browser); // header browser.SetFieldValue("m_ListHeaderRect", breadcrumbsRect); browser.InvokeMethod("BreadCrumbBar"); breadcrumbsRect.Draw(breadcrumbsTint); topGapRect.Draw(topGapColor); breadcrumbsRect.SetHeightFromBottom(1).Draw(Greyscale(.14f)); // footer browser.SetFieldValue("m_BottomBarRect", footerRect); browser.InvokeMethod("BottomBar"); // list area browser.GetFieldValue("m_ListArea").InvokeMethod("OnGUI", listAreaRect, m_ListKeyboardControlID); // block grid size changes when ctrl-shift-scrolling if (curEvent.holdingCmdOrCtrl) browser.GetFieldValue("m_ListArea").SetMemberValue("gridSize", startGridSize); browser.SetFieldValue("m_StartGridSize", browser.GetFieldValue("m_ListArea").GetMemberValue("gridSize")); browser.InvokeMethod("HandleContextClickInListArea", listAreaRect); browser.InvokeMethod("HandleCommandEvents"); } setRootForOneColumn(); handleFolderChange(); oneColumn(); twoColumns(); } static void UpdateWrappingForBrowser(EditorWindow browser) { if (!browser.hasFocus) return; if (mi_VFavorites_CanBrowserBeWrapped != null && mi_VFavorites_CanBrowserBeWrapped.Invoke(null, new[] { browser }).Equals(false)) return; var isLocked = browser.GetMemberValue("isLocked"); var isWrapped = browser.GetMemberValue("m_Parent").GetMemberValue("m_OnGUI").Method == mi_WrappedBrowserOnGUI; void wrap() { if (!isLocked) return; if (isWrapped) return; var hostView = browser.GetMemberValue("m_Parent"); var newDelegate = typeof(VTabs).GetMethod(nameof(WrappedBrowserOnGUI), maxBindingFlags).CreateDelegate(t_EditorWindowDelegate, browser); hostView.SetMemberValue("m_OnGUI", newDelegate); browser.Repaint(); browser.SetMemberValue("useTreeViewSelectionInsteadOfMainSelection", false); } void unwrap() { if (isLocked) return; if (!isWrapped) return; var hostView = browser.GetMemberValue("m_Parent"); var originalDelegate = hostView.InvokeMethod("CreateDelegate", "OnGUI"); hostView.SetMemberValue("m_OnGUI", originalDelegate); browser.Repaint(); } wrap(); unwrap(); } static void UpdateWrappingForAllBrowsers() { foreach (var r in allBrowsers) UpdateWrappingForBrowser(r); } static void HideTabScrollerButtons() { void getStyles() { if (leftScrollerStyle != null && rightScrollerStyle != null) return; if (!guiStylesInitialized) TryInitializeGuiStyles(); if (!guiStylesInitialized) return; if (typeof(GUISkin).GetFieldValue("current")?.GetFieldValue>("m_Styles")?.ContainsKey("dragtab scroller prev") != true) return; if (typeof(GUISkin).GetFieldValue("current")?.GetFieldValue>("m_Styles")?.ContainsKey("dragtab scroller next") != true) return; var t_Styles = typeof(Editor).Assembly.GetType("UnityEditor.DockArea+Styles"); leftScrollerStyle = t_Styles.GetFieldValue("tabScrollerPrevButton"); rightScrollerStyle = t_Styles.GetFieldValue("tabScrollerNextButton"); } void createTexture() { if (clearTexture != null) return; clearTexture = new Texture2D(1, 1); clearTexture.hideFlags = HideFlags.DontSave; clearTexture.SetPixel(0, 0, Color.clear); clearTexture.Apply(); } void assignTexture() { if (leftScrollerStyle == null) return; if (rightScrollerStyle == null) return; leftScrollerStyle.normal.background = clearTexture; rightScrollerStyle.normal.background = clearTexture; } getStyles(); createTexture(); assignTexture(); } static GUIStyle leftScrollerStyle; static GUIStyle rightScrollerStyle; static Texture2D clearTexture; static void ClosePropertyEditorsWithNonLoadableObjects() { foreach (var propertyEditor in allPropertyEditors) if (propertyEditor.GetMemberValue("m_InspectedObject") == null) propertyEditor.Close(); } static void LoadPropertyEditorInspectedObjects() { foreach (var propertyEditor in allPropertyEditors) propertyEditor.InvokeMethod("LoadPersistedObject"); } static void EnsureActiveTabsVisibleOnScroller() { foreach (var dockArea in allDockAreas) { if (!guis_byDockArea.TryGetValue(dockArea, out var gui)) continue; var scrollPos = gui.GetTargetScrollPosition(); if (!scrollPos.Approx(0)) scrollPos += gui.nonZeroTabScrollOffset; dockArea.SetFieldValue("m_ScrollOffset", scrollPos); } } public static void RepaintAllDockAreas() { foreach (var dockarea in allDockAreas) dockarea.InvokeMethod("Repaint"); } [UnityEditor.Callbacks.PostProcessBuild] static void OnBuild(BuildTarget _, string __) { EditorApplication.delayCall += LoadPropertyEditorInspectedObjects; EditorApplication.delayCall += UpdateAllPropertyEditorTitles; } static void OnDomainReloaded() { toCallInGUI += UpdateWrappingForAllBrowsers; toCallInGUI += UpdateAllBrowserTitles; } static void OnSceneOpened(Scene _, OpenSceneMode __) { LoadPropertyEditorInspectedObjects(); ClosePropertyEditorsWithNonLoadableObjects(); UpdateAllPropertyEditorTitles(); } static void OnProjectLoaded() { toCallInGUI += EnsureActiveTabsVisibleOnScroller; UpdateAllPropertyEditorTitles(); } static void OnFocusedWindowChanged() { if (EditorWindow.focusedWindow?.GetType() == t_ProjectBrowser) UpdateWrappingForBrowser(EditorWindow.focusedWindow); } static void OnWindowUnmaximized() { UpdateAllPropertyEditorTitles(); UpdateAllBrowserTitles(); UpdateWrappingForAllBrowsers(); EnsureActiveTabsVisibleOnScroller(); } static void CheckIfFocusedWindowChanged() { if (prevFocusedWindow != EditorWindow.focusedWindow) OnFocusedWindowChanged(); prevFocusedWindow = EditorWindow.focusedWindow; } static EditorWindow prevFocusedWindow; static void CheckIfWindowWasUnmaximized() { var isMaximized = EditorWindow.focusedWindow?.maximized == true; if (!isMaximized && wasMaximized) OnWindowUnmaximized(); wasMaximized = isMaximized; } static bool wasMaximized; static void OnSomeGUI() { toCallInGUI?.Invoke(); toCallInGUI = null; CheckIfFocusedWindowChanged(); } static void ProjectWindowItemOnGUI(string _, Rect __) => OnSomeGUI(); static void HierarchyWindowItemOnGUI(int _, Rect __) => OnSomeGUI(); static Action toCallInGUI; static void DelayCallLoop() { UpdateAllBrowserTitles(); UpdateWrappingForAllBrowsers(); HideTabScrollerButtons(); EditorApplication.delayCall -= DelayCallLoop; EditorApplication.delayCall += DelayCallLoop; } static void Update() { CheckIfFocusedWindowChanged(); CheckIfWindowWasUnmaximized(); } static void ComponentTabHeaderGUI(Editor editor) { if (editor.target is not Component component) return; var headerRect = ExpandWidthLabelRect(height: 0).MoveY(-48).SetHeight(50).AddWidthFromMid(8); var nameRect = headerRect.MoveX(43).MoveY(5).SetHeight(20).SetXMax(headerRect.xMax - 50); var subtextRect = headerRect.MoveX(43).MoveY(22).SetHeight(20); void hideName() { var maskRect = headerRect.AddWidthFromRight(-45).AddWidth(-50); var maskColor = Greyscale(isDarkTheme ? .24f : .8f); maskRect.Draw(maskColor); } void name() { SetLabelFontSize(13); GUI.Label(nameRect, GetComponentName(component)); ResetLabelStyle(); } void componentOf() { SetGUIEnabled(false); GUI.Label(subtextRect, "Component of"); ResetGUIEnabled(); } void goName() { var goNameRect = subtextRect.MoveX("Component of ".GetLabelWidth() - 3).SetWidth(component.gameObject.name.GetLabelWidth(isBold: true)); goNameRect.MarkInteractive(); SetGUIEnabled(goNameRect.IsHovered() && !mousePressedOnGoName); SetLabelBold(); GUI.Label(goNameRect, component.gameObject.name); ResetGUIEnabled(); ResetLabelStyle(); if (curEvent.isMouseDown && goNameRect.IsHovered()) { mousePressedOnGoName = true; curEvent.Use(); } if (curEvent.isMouseUp) { if (mousePressedOnGoName) EditorGUIUtility.PingObject(component.gameObject); mousePressedOnGoName = false; curEvent.Use(); } if (curEvent.isMouseLeaveWindow || (!curEvent.isLayout && !goNameRect.Resize(1).IsHovered())) mousePressedOnGoName = false; } hideName(); name(); componentOf(); goName(); Space(-4); } static bool mousePressedOnGoName; static string GetComponentName(Component component) { if (!component) return ""; var name = new GUIContent(EditorGUIUtility.ObjectContent(component, component.GetType())).text; name = name.Substring(name.LastIndexOf('(') + 1); name = name.Substring(0, name.Length - 1); return name; } static void SetupExtraScrollerSpace() { var action = t_WindowAction.GetMethod("CreateWindowMenuItem", maxBindingFlags).Invoke(null, new[] { "vTabs dummy for creating extra space for + button", null, null }); var extraSpaceAmount = 21f; action.SetMemberValue("width", extraSpaceAmount); var mi_ShouldCreateExtraSpace = typeof(VTabs).GetMethod("ShouldCreateExtraSpace", maxBindingFlags); var funcDelegate = Delegate.CreateDelegate(t_ValidateHandler, mi_ShouldCreateExtraSpace); action.SetMemberValue("validateHandler", funcDelegate); var defaultActions = t_HostView.GetMemberValue("s_windowActions") ?? t_HostView.InvokeMethod("FetchWindowActionFromAttribute"); var newActions = System.Array.CreateInstance(t_WindowAction, defaultActions.Length + 1); System.Array.Copy(defaultActions, newActions, defaultActions.Length); newActions.SetValue(action, defaultActions.Length); t_HostView.SetMemberValue("s_windowActions", newActions); } static bool ShouldCreateExtraSpace(EditorWindow window, object _) => VTabsMenu.addTabButtonEnabled && window == window.GetDockArea().GetTabs()?.Last(); static void TryInitializeGuiStyles() => EditorWindow.focusedWindow?.SendEvent(EditorGUIUtility.CommandEvent("")); static bool guiStylesInitialized => typeof(GUI).GetFieldValue("s_Skin") != null; static Object GetDockArea(this EditorWindow window) { var parent = window?.GetFieldValue("m_Parent"); if (parent?.GetType() == t_DockArea) return parent; else return null; } public static List GetTabs(this Object dockArea) => dockArea?.GetFieldValue>("m_Panes"); public static string GetLockedFolderPath_oneColumn(this EditorWindow browser) { var path = browser.GetMemberValue("m_LastFolders")?.FirstOrDefault(); if (path == null || path == "Assets") path = browser.GetMemberValue("m_SearchFilter")?.GetMemberValue("m_Folders")?.FirstOrDefault() ?? "Assets"; // to migrate locked folder paths from 2.0.14 return path; // unlike in two column layout, there's no such concept as active folder path in one column // so we have to serialize locked folder path in some unused field // m_LastFolders appears to work fine for this purpose // m_SearchFilter was used before v2.0.15 but could get changed when moving/creating/deleting assets } public static void SetLockedFolderPath_oneColumn(this EditorWindow browser, string folderPath) { browser.SetMemberValue("m_LastFolders", new[] { folderPath }); } [System.Serializable] public class TabInfo { public TabInfo(EditorWindow window) { typeName = window.GetType().Name; originalDockArea = window.GetDockArea(); originalTabIndex = window.GetDockArea().GetTabs().IndexOf(window); wasFocused = window.hasFocus; originalTitle = window.titleContent.text; menuItemName = window.titleContent.text.Replace("/", " \u2215 ").Trim(' '); if (isBrowser) { isLocked = window.GetPropertyValue("isLocked"); savedGridSize = window.GetFieldValue("m_StartGridSize"); isGridSizeSaved = true; savedLayout = window.GetMemberValue("m_ViewMode"); isLayoutSaved = true; var folderPath = savedLayout == 0 ? window.GetLockedFolderPath_oneColumn() // one column : window.InvokeMethod("GetActiveFolderPath"); // two columns folderGuid = folderPath.ToGuid(); } if (isPropertyEditor) globalId = new GlobalID(window.GetMemberValue("m_GlobalObjectId")); } public TabInfo(Object lockTo) { isLocked = true; typeName = lockTo is DefaultAsset ? t_ProjectBrowser.Name : t_PropertyEditor.Name; if (isBrowser) folderGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(lockTo)); if (isPropertyEditor) globalId = lockTo.GetGlobalID(); #if UNITY_2021_2_OR_NEWER if (isPropertyEditor) if (StageUtility.GetCurrentStage() is PrefabStage && globalId.ToString().Contains("00000000000000000000000000000000")) lockedPrefabAssetObject = lockTo; #endif } public TabInfo(string typeName, string menuItemName) { this.typeName = typeName; this.menuItemName = menuItemName; } public string typeName; public string menuItemName; public object originalDockArea; public int originalTabIndex; public string originalTitle; public bool wasFocused; public bool isBrowser => typeName == t_ProjectBrowser.Name; public bool isLocked; public string folderGuid = ""; public int savedGridSize; public int savedLayout; public bool isGridSizeSaved = false; public bool isLayoutSaved = false; public bool isPropertyEditor => typeName == t_PropertyEditor.Name; public GlobalID globalId; public Object lockedPrefabAssetObject; } [System.Serializable] class TabInfoList { public List list = new List(); } [InitializeOnLoadMethod] static void Init() { if (VTabsMenu.pluginDisabled) return; EditorApplication.update -= UpdateGUIs; EditorApplication.update += UpdateGUIs; // shortcuts var globalEventHandler = typeof(EditorApplication).GetFieldValue("globalEventHandler"); typeof(EditorApplication).SetFieldValue("globalEventHandler", (globalEventHandler - Shortcuts) + Shortcuts); // component tabs Editor.finishedDefaultHeaderGUI -= ComponentTabHeaderGUI; Editor.finishedDefaultHeaderGUI += ComponentTabHeaderGUI; // state change detectors var projectWasLoaded = typeof(EditorApplication).GetFieldValue("projectWasLoaded"); typeof(EditorApplication).SetFieldValue("projectWasLoaded", (projectWasLoaded - OnProjectLoaded) + OnProjectLoaded); UnityEditor.SceneManagement.EditorSceneManager.sceneOpened -= OnSceneOpened; UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened; EditorApplication.projectWindowItemOnGUI -= ProjectWindowItemOnGUI; EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUI; EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowItemOnGUI; EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI; EditorApplication.delayCall -= DelayCallLoop; EditorApplication.delayCall += DelayCallLoop; EditorApplication.update -= Update; EditorApplication.update += Update; EditorApplication.quitting -= VTabsCache.Save; EditorApplication.quitting += VTabsCache.Save; // EditorApplication.delayCall += () => VTabsAddTabWindow.UpdateAllEntries(); EditorApplication.delayCall += () => UpdateStyleSheet(); SetupExtraScrollerSpace(); OnDomainReloaded(); } public static IEnumerable allBrowsers => _allBrowsers ??= t_ProjectBrowser.GetFieldValue("s_ProjectBrowsers").Cast(); public static IEnumerable _allBrowsers; public static IEnumerable allPropertyEditors => Resources.FindObjectsOfTypeAll(t_PropertyEditor).Where(r => r.GetType().BaseType == typeof(EditorWindow)).Cast(); public static List allEditorWindows { get { if (typeof(EditorWindow).GetPropertyInfo("activeEditorWindows") == null) // this variable doesn't exist in early 2022.3 versions, even though UnityCsReference repo says it does return Resources.FindObjectsOfTypeAll(typeof(EditorWindow)).Cast().ToList(); return _allEditorWindows ?? typeof(EditorWindow).GetMemberValue>("activeEditorWindows"); } } public static List _allEditorWindows; public static IEnumerable allDockAreas => allEditorWindows.Where(r => r.hasFocus && r.docked && !r.maximized).Select(r => r.GetMemberValue("m_Parent")); public static Type t_DockArea = typeof(Editor).Assembly.GetType("UnityEditor.DockArea"); public static Type t_PropertyEditor = typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor"); public static Type t_ProjectBrowser = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); public static Type t_SceneHierarchyWindow = typeof(Editor).Assembly.GetType("UnityEditor.SceneHierarchyWindow"); public static Type t_InspectorWindow = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow"); public static Type t_WindowAction = typeof(Editor).Assembly.GetType("UnityEditor.WindowAction"); public static Type t_HostView = typeof(Editor).Assembly.GetType("UnityEditor.HostView"); public static Type t_EditorWindowDelegate = t_HostView.GetNestedType("EditorWindowDelegate", maxBindingFlags); public static Type t_ValidateHandler = t_WindowAction.GetNestedType("ValidateHandler", maxBindingFlags); public static Type t_EditorWindowShowButtonDelegate = t_HostView.GetNestedType("EditorWindowShowButtonDelegate", maxBindingFlags); public static Type t_VHierarchy = Type.GetType("VHierarchy.VHierarchy") ?? Type.GetType("VHierarchy.VHierarchy, VHierarchy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); public static Type t_VFolders = Type.GetType("VFolders.VFolders") ?? Type.GetType("VFolders.VFolders, VFolders, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); public static Type t_VFavorites = Type.GetType("VFavorites.VFavorites") ?? Type.GetType("VFavorites.VFavorites, VFavorites, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); public static MethodInfo mi_WrappedBrowserOnGUI = typeof(VTabs).GetMethod(nameof(WrappedBrowserOnGUI), maxBindingFlags); public static MethodInfo mi_VFolders_GetIcon = t_VFolders?.GetMethod("GetSmallFolderIcon_forVTabs", maxBindingFlags); public static MethodInfo mi_VHierarchy_GetIcon = t_VHierarchy?.GetMethod("GetIcon_forVTabs", maxBindingFlags); public static MethodInfo mi_VFavorites_BeforeWindowCreated = t_VFavorites?.GetMethod("BeforeWindowCreated_byVTabs", maxBindingFlags); public static MethodInfo mi_VFavorites_CanBrowserBeWrapped = t_VFavorites?.GetMethod("CanBrowserBeWrapped_byVTabs", maxBindingFlags); const string version = "2.1.1"; } } #endif