#if UNITY_EDITOR using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using UnityEditor.ShortcutManagement; using System.Reflection; using System.Linq; using UnityEngine.UIElements; using Type = System.Type; using static VFavorites.Libs.VUtils; using static VFavorites.Libs.VGUI; using static VFavorites.VFavoritesData; namespace VFavorites { public static class VFavorites { static void WrappedOnGUI(object _) { if (wrappedBrowser?.GetType() != t_BrowserWindow) { originalBrowserGUI(); return; } void createData() { if (data) return; data = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(data, GetScriptPath("VFavorites").GetParentPath().CombinePath("vFavorites Data.asset")); // // migrateDataFromV1 // if (!EditorPrefsCached.HasKey("vFavorites-guids-" + GetProjectId())) return; // if (EditorPrefsCached.HasKey("vHierarchy-dataMigrationFromV1Attempted-" + GetProjectId())) return; // EditorPrefsCached.SetBool("vHierarchy-dataMigrationFromV1Attempted-" + GetProjectId(), true); // if (!data.pages.Any()) // data.pages.Add(new Page("Page 1")); // var guidsFromV1 = EditorPrefsCached.GetString("vFavorites-guids-" + GetProjectId()).Split('-').Where(r => r != "").ToList(); // foreach (var guid in guidsFromV1) // if (AssetDatabase.LoadAssetAtPath(guid.ToPath()) is Object obj) // data.pages.First().items.Add(new Item(obj)); // data.Dirty(); // data.Save(); } void closeNavbarSearch() { if (!curEvent.isLayout) return; if (t_VFolders == null) return; if (GUI.GetNameOfFocusedControl() == "navbar search field") GUI.FocusControl(null); if (!wrappedBrowser.GetMemberValue("m_SearchFieldText").IsNullOrEmpty()) { wrappedBrowser.SetMemberValue("m_SearchFieldText", ""); wrappedBrowser.GetMemberValue("m_SearchFilter").SetMemberValue("m_NameFilter", ""); wrappedBrowser.InvokeMethod("UpdateSearchDelayed"); GUIUtility.keyboardControl = 0; } // mouse input on vFavorites throws exceptions if search is active on vFolders navbar // here we close search to avoid this } void pageScroll() { if (!curEvent.isScroll) return; if (!VFavoritesMenu.pageScrollEnabled) return; var scrollDelta = curEvent.mouseDelta.y; if (scrollDelta == 0 && curEvent.holdingShift) scrollDelta = curEvent.mouseDelta.x; if (scrollDelta < 0 && data.curPageIndex == 0) return; if (scrollDelta < 0) { data.curPageIndex--; prevPageButtonBrightness = 2; } if (scrollDelta > 0) { data.curPageIndex++; nextPageButtonBrightness = 2; } CancelDragging(); CancelRowAnimations(); curEvent.Use(); } void background() { var color = isDarkTheme ? Greyscale(.2f) : Greyscale(.78f); totalRect_groupSpace.Draw(color); } void pages() { void page(Rect pageRect, Page page) { void findSelectedItem() { if (!curEvent.isLayout) return; foreach (var item in page.items) item.isSelected = false; if (draggingItem) return; if (mousePresesdOnItem) return; if (page.lastItemDragTime_ticks > page.lastItemSelectTime_ticks) return; Item lastSelectedItem = null; foreach (var item in page.items) { if (!item.isLoadable) continue; if (lastSelectedItem?.lastSelectTime_ticks > item.lastSelectTime_ticks) continue; var isSelected = false; if (item.isFolder) { var targetBrowser = isWrappedBrowserLocked && isOneColumn ? allBrowsers.FirstOrDefault(r => !r.GetMemberValue("isLocked")) ?? wrappedBrowser : wrappedBrowser; if (targetBrowser.GetFieldValue("m_ViewMode") == 1) isSelected = targetBrowser.InvokeMethod("GetActiveFolderPath") == item.assetPath; else isSelected = Selection.activeObject == item.obj; } if (!item.isFolder) isSelected = Selection.activeObject == item.obj; if (isSelected) lastSelectedItem = item; } if (lastSelectedItem != null) lastSelectedItem.isSelected = true; } void rows() { void row(float y, Item item) { var rowRect = pageRect.SetHeight(rowHeight).SetY(y).SetX(0); var iconOffset = 6; var iconSize = 25 * Mathf.Min(1, data.rowScale); var nameOffset = 3; var deletedOrNotLoadedLabelOffset = 1; float highlightAmount = 0f; void set_highlightAmount() { if (animatingDroppedItem && item == droppedItem) highlightAmount = droppedItemHighlightAmount; if (item.isSelected) highlightAmount = 1; if (draggingItem && item == draggedItem) highlightAmount = 1; if (mousePresesdOnItem && item == pressedItem) highlightAmount = 1; } void shadow() { if (item != draggedItem && item != droppedItem) return; var amount = item == droppedItem ? droppedItemShadowAmount : 1; if (amount.Approx(0)) return; rowRect.AddWidthFromMid(30).DrawBlurred(Greyscale(0, .55f * amount), 22); } void background() { var evenColor = isDarkTheme ? Greyscale(.249f) : Greyscale(.82f); var oddColor = isDarkTheme ? Greyscale(.228f) : Greyscale(.85f); var highlightedColor = isDarkTheme ? Greyscale(.335f) : Greyscale(.9f); var rowColor = Lerp(evenColor, oddColor, rowRect.y.PingPong(rowHeight) / rowHeight); Lerp(ref rowColor, highlightedColor, highlightAmount); rowRect.Draw(rowColor); } void icon() { var iconRect = rowRect.MoveX(iconOffset).SetWidth(iconSize).SetHeightFromMid(iconSize); if (item.isSceneGameObject) iconRect = iconRect.MoveX(1).Resize(.5f); void asset() { if (!item.isAsset) return; var iconTexture = item.isLoadable ? AssetPreview.GetAssetPreview(item.obj) ?? AssetPreview.GetMiniThumbnail(item.obj) : AssetPreview.GetMiniTypeThumbnail(item.type); GUI.DrawTexture(iconRect, iconTexture); } void sceneGameObject() { if (!item.isSceneGameObject) return; void getIconNameFromAssetPreview() { if (!item.isLoadable) return; item.sceneGameObjectIconName = AssetPreview.GetMiniThumbnail(item.obj).name; } void getIconNameFromVHierarchy() { if (!item.isLoadable) return; if (!(item.obj is GameObject gameObject)) return; if (mi_VHierarchy_GetIconName == null) return; var iconNameFromVHierarchy = (string)mi_VHierarchy_GetIconName.Invoke(null, new object[] { gameObject }); if (!iconNameFromVHierarchy.IsNullOrEmpty()) item.sceneGameObjectIconName = iconNameFromVHierarchy; } getIconNameFromAssetPreview(); getIconNameFromVHierarchy(); var iconTexture = EditorGUIUtility.IconContent(item.sceneGameObjectIconName.IsNullOrEmpty() ? "GameObject icon" : item.sceneGameObjectIconName).image; GUI.DrawTexture(iconRect, iconTexture); } void folder() { if (!item.isFolder) return; iconRect = iconRect.Resize(-1.5f); void drawNormal() { if (isDarkTheme) if (highlightAmount == 1) return; GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("Folder icon").image); } void drawHighlighted() { if (!isDarkTheme) return; if (highlightAmount != 1) return; SetGUIColor(Greyscale(.84f)); GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("Folder On icon").image); ResetGUIColor(); } void drawViaVFolders() { mi_VFolders_DrawBigFolderIcon?.Invoke(null, new object[] { iconRect, item.globalId.guid }); } drawNormal(); drawHighlighted(); drawViaVFolders(); } asset(); sceneGameObject(); folder(); } void name() { var nameRect = rowRect.MoveX(iconOffset + iconSize + nameOffset).MoveY(-.5f).SetHeightFromMid(16); void normal() { if (isDarkTheme) if (highlightAmount == 1) return; GUI.Label(nameRect, item.name); } void highlighted() { if (!isDarkTheme) return; if (highlightAmount != 1) return; if (!curEvent.isRepaint) return; SetGUIColor(Greyscale(.91f)); GUI.skin.GetStyle("WhiteLabel").Draw(nameRect, item.name, false, false, false, false); ResetGUIColor(); } normal(); highlighted(); } void deletedOrNotLoaded() { var labelRect = rowRect.MoveX(iconOffset + iconSize + nameOffset + item.name.GetLabelWidth() + deletedOrNotLoadedLabelOffset).MoveY(.5f); SetGUIEnabled(false); SetLabelFontSize(10); if (item.isDeleted) GUI.Label(labelRect, "Deleted"); else if (!item.isLoadable) GUI.Label(labelRect, "Not loaded"); ResetLabelStyle(); ResetGUIEnabled(); } void crossButton() { if (!rowRect.IsHovered()) return; if (draggingItem) return; // if (mousePresesdOnItem) return; // idk var buttonRect = rowRect.SetWidthFromRight(0).MoveX(-crossButtonOffsetFromRight).SetWidthFromMid(crossButtonSize); var iconRect = buttonRect.SetSizeFromMid(16); var normalColor = Greyscale(item.isSelected ? .48f : .4f); var hoveredColor = isDarkTheme ? Greyscale(.8f) : normalColor; var pressedColor = Greyscale(.6f); SetGUIColor(buttonRect.IsHovered() ? (mousePressed ? pressedColor : hoveredColor) : normalColor); GUI.Label(iconRect, EditorGUIUtility.IconContent("CrossIcon")); ResetGUIColor(); buttonRect.MarkInteractive(); if (!mousePressedOnCrossButtonArea) return; if (!curEvent.isMouseUp) return; if (!buttonRect.IsHovered()) return; CancelRowAnimations(); data.curPage.rowGaps[data.curPage.items.IndexOf(item)] = rowHeight; data.curPage.items.Remove(item); data.Dirty(); data.Save(); curEvent.Use(); } void click() { if (!rowRect.IsHovered()) return; if (!curEvent.isMouseUp) return; curEvent.Use(); if (draggingItem) return; if (mouseDragDistance > 2) return; if (!item.isLoadable) return; SelectItem(item); } void doubleclick() { if (!rowRect.IsHovered()) return; if (!doubleclickUnhandled) return; OpenItem(item); doubleclickUnhandled = false; } rowRect.MarkInteractive(); set_highlightAmount(); shadow(); background(); icon(); name(); deletedOrNotLoaded(); crossButton(); click(); doubleclick(); } void normalRow(int i) { Space(page.rowGaps[i]); Space(rowHeight); if (page.items[i] == droppedItem && animatingDroppedItem && page == data.curPage) return; row(lastRect.y, page.items[i]); } void draggedRow() { if (!draggingItem) return; if (page != data.curPage) return; row(draggedItemY_rowsSpace, draggedItem); } void droppedRow() { if (!animatingDroppedItem) return; if (page != data.curPage) return; row(droppedItemY_rowsSpace, droppedItem); } if (curEvent.isRepaint && skipNextRepaint) { skipNextRepaint = false; return; } GUILayout.BeginArea(pageRect); page.scrollPos = EditorGUILayout.BeginScrollView(new Vector2(0, page.scrollPos), GUIStyle.none, GUIStyle.none).y; for (int i = 0; i < page.items.Count; i++) normalRow(i); Space(page.rowGaps.Last()); Space(60); draggedRow(); droppedRow(); EditorGUILayout.EndScrollView(); GUILayout.EndArea(); } void curtains() { var height = 25; var color = isDarkTheme ? Greyscale(.2f) : Greyscale(.78f); pageRect.SetHeight(height).DrawCurtainDown(color.SetAlpha((page.scrollPos / 20).Smoothstep())); pageRect.SetHeightFromBottom(height).DrawCurtainUp(color); } void tutor() { if (page.items.Any() || draggingItem) return; SetGUIEnabled(false); SetLabelFontSize(11); SetLabelAlignmentCenter(); GUI.Label(pageRect.MoveY(-13), "Drop folders, assets"); GUI.Label(pageRect.MoveY(5), "or GameObjects"); ResetGUIEnabled(); ResetLabelStyle(); } findSelectedItem(); rows(); curtains(); tutor(); } var spaceBetweenPages = 10; if (pagesScrollPos == -1) pagesScrollPos = data.curPageIndex; while (data.pages.Count <= pagesScrollPos.CeilToInt()) data.pages.Add(new Page("Page " + (data.pages.Count + 1))); for (int i = pagesScrollPos.FloorToInt(); i <= pagesScrollPos.CeilToInt(); i++) page(totalRect_groupSpace.SetX((totalRect_groupSpace.width + spaceBetweenPages) * (i - pagesScrollPos)), data.pages[i]); } void widget() { var widthToAdd = 48; var height = 24; var distToBottom = 13; var color = isDarkTheme ? Greyscale(.1f) : Greyscale(.85f); var shadowSize = 10; var shadowAlpha = .23f; var chevronSize = 15; var chevronOffset = 12; var chevronBrightness = .5f; var textSize = 11; var textBrightness = .65f; widgetRect_groupSpace = totalRect_groupSpace.SetWidthFromMid(data.curPage.name.GetLabelWidth(textSize) + widthToAdd).SetHeightFromBottom(height).MoveY(-distToBottom); void shadow() { widgetRect_groupSpace.Resize(1).DrawBlurred(Greyscale(0f, shadowAlpha), shadowSize); } void background() { widgetRect_groupSpace.DrawWithRoundedCorners(color, 1223); } void nameLabel() { if (renamingPage) return; var buttonRect = widgetRect_groupSpace.SetWidthFromMid(data.curPage.name.GetLabelWidth(textSize) - 2); buttonRect.MarkInteractive(); var activated = curEvent.isMouseUp && buttonRect.IsHovered(); var brightness = !buttonRect.IsHovered() ? textBrightness : (mousePressed ? .75f : 1); SetGUIColor(Greyscale(brightness)); SetLabelAlignmentCenter(); SetLabelFontSize(textSize); SetLabelBold(); GUI.Label(widgetRect_groupSpace, data.curPage.name); ResetGUIColor(); ResetLabelStyle(); if (!activated) return; renamingPage = true; prevPageName = data.curPage.name; curEvent.Use(); } void leftButton() { if (renamingPage) return; var iconRect = widgetRect_groupSpace.SetWidth(chevronOffset * 2).SetSizeFromMid(chevronSize, chevronSize); var buttonRect = Rect.zero.SetX(0).SetY(widgetRect_groupSpace.y).SetXMax(iconRect.xMax + 4).SetYMax(totalRect_groupSpace.yMax); buttonRect.MarkInteractive(); var active = data.curPageIndex > 0; var activated = curEvent.isMouseUp && buttonRect.IsHovered(); var brightness = prevPageButtonBrightness * (!buttonRect.IsHovered() || !active ? chevronBrightness : (mousePressed ? .75f : 1)); SetGUIColor(Greyscale(brightness)); GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("NodeChevronLeft@2x").image); ResetGUIColor(); if (!activated) return; curEvent.Use(); if (!active) return; CancelDragging(); CancelRowAnimations(); data.curPageIndex--; } void rightButton() { if (renamingPage) return; var iconRect = widgetRect_groupSpace.SetWidthFromRight(chevronOffset * 2).SetSizeFromMid(chevronSize, chevronSize); var buttonRect = Rect.zero.SetX(iconRect.x - 6).SetY(widgetRect_groupSpace.y).SetXMax(totalRect_groupSpace.xMax).SetYMax(totalRect_groupSpace.yMax); buttonRect.MarkInteractive(); var active = true;// data.curPageIndex < data.pages.Count - 1 && data.curPage.items.Any(); var activated = curEvent.isMouseUp && buttonRect.IsHovered(); var brightness = nextPageButtonBrightness * (!buttonRect.IsHovered() || !active ? chevronBrightness : (mousePressed ? .75f : 1)); SetGUIColor(Greyscale(brightness)); GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("NodeChevronRight@2x").image); ResetGUIColor(); if (!activated) return; curEvent.Use(); if (!active) return; CancelDragging(); CancelRowAnimations(); data.curPageIndex++; } void nameTextField() { if (!renamingPage) return; var textFieldRect = widgetRect_groupSpace.AddHeightFromMid(-2).SetWidthFromMid(data.curPage.name.GetLabelWidth(textSize) + 4); var s = new GUIStyle(GUI.skin.textField); s.alignment = TextAnchor.MiddleCenter; s.fontSize = 11; EditorGUIUtility.AddCursorRect(textFieldRect, MouseCursor.CustomCursor); GUI.SetNextControlName("asdasdasd"); EditorGUI.FocusTextInControl("asdasdasd"); data.curPage.name = EditorGUI.TextField(textFieldRect, data.curPage.name, s); if (data.curPage.name.IsNullOrEmpty()) data.curPage.name = "Page " + (data.curPageIndex + 1); EditorGUIUtility.AddCursorRect(textFieldRect, MouseCursor.CustomCursor); } void cancelRename_button() { if (!renamingPage) return; var iconRect = widgetRect_groupSpace.SetWidth(chevronOffset * 2).SetSizeFromMid(chevronSize - 2); var buttonRect = iconRect.SetSizeFromMid(25, 25); buttonRect.MarkInteractive(); var brightness = !buttonRect.IsHovered() ? chevronBrightness : (mousePressed ? .75f : 1); SetGUIColor(Greyscale(brightness)); GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("CrossIcon").image); ResetGUIColor(); if (!curEvent.isMouseUp) return; if (!buttonRect.IsHovered()) return; curEvent.Use(); data.curPage.name = prevPageName; renamingPage = false; } void acceptRename_button() { if (!renamingPage) return; var iconRect = widgetRect_groupSpace.SetWidthFromRight(chevronOffset * 2).SetSizeFromMid(chevronSize - 0); var buttonRect = iconRect.SetSizeFromMid(25, 25); buttonRect.MarkInteractive(); var brightness = !buttonRect.IsHovered() ? chevronBrightness : (mousePressed ? .75f : 1); SetGUIColor(Greyscale(brightness)); GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("check").image); ResetGUIColor(); if (!curEvent.isMouseUp) return; if (!buttonRect.IsHovered()) return; curEvent.Use(); renamingPage = false; data.Dirty(); data.Save(); } void acceptRename_enterKey() { if (!renamingPage) return; if (curEvent.keyCode != KeyCode.Return) return; renamingPage = false; data.Dirty(); data.Save(); } void cancelRename_escapeKey() { if (!renamingPage) return; if (curEvent.keyCode != KeyCode.Escape) return; data.curPage.name = prevPageName; renamingPage = false; } shadow(); background(); leftButton(); rightButton(); nameLabel(); nameTextField(); cancelRename_button(); acceptRename_button(); acceptRename_enterKey(); cancelRename_escapeKey(); } void keys() { if (isWrappedBrowserLocked && !totalRect_browserSpace.IsHovered()) return; void prevPage() { if (!curEvent.isKeyDown) return; if (curEvent.keyCode != KeyCode.LeftArrow) return; if (!VFavoritesMenu.arrowKeysEnabled) return; if (data.curPageIndex == 0) return; CancelDragging(); CancelRowAnimations(); data.curPageIndex--; prevPageButtonBrightness = 2; curEvent.Use(); } void nextPage() { if (curEvent.keyCode != KeyCode.RightArrow) return; if (!curEvent.isKeyDown) return; if (!VFavoritesMenu.arrowKeysEnabled) return; CancelDragging(); CancelRowAnimations(); data.curPageIndex++; nextPageButtonBrightness = 2; curEvent.Use(); } void selectPrev() { if (!curEvent.isKeyDown) return; if (curEvent.keyCode != KeyCode.UpArrow) return; if (!VFavoritesMenu.arrowKeysEnabled) return; var iToSelect = data.curPage.items.IndexOfFirst(r => r.isSelected) - 1; if (iToSelect < 0) iToSelect = data.curPage.items.LastIndex(); if (iToSelect.IsInRangeOf(data.curPage.items)) SelectItem(data.curPage.items[iToSelect]); curEvent.Use(); } void selectNext() { if (!curEvent.isKeyDown) return; if (curEvent.keyCode != KeyCode.DownArrow) return; if (!VFavoritesMenu.arrowKeysEnabled) return; var iToSelect = data.curPage.items.IndexOfFirst(r => r.isSelected) + 1; if (iToSelect >= data.curPage.items.Count) iToSelect = 0; if (iToSelect.IsInRangeOf(data.curPage.items)) SelectItem(data.curPage.items[iToSelect]); curEvent.Use(); } void numberKeys() { if (!curEvent.isKeyDown) return; if (!VFavoritesMenu.numberKeysEnabled) return; if (EditorGUIUtility.editingTextField) return; var i = ((int)curEvent.keyCode - 48); if (i == 0) i = 10; if (!i.IsInRange(1, 10)) return; data.curPageIndex = i - 1; curEvent.Use(); } prevPage(); nextPage(); selectPrev(); selectNext(); numberKeys(); } void doOriginalGUIFirst_() { originalBrowserGUI(); GUI.BeginGroup(totalRect_browserSpace); GUI.color = GUI.color.SetAlpha(currentOpacity); background(); pages(); widget(); GUI.color = GUI.color.SetAlpha(1); GUI.EndGroup(); } void doVFavoritesGUIFirst_() { keys(); pageScroll(); GUI.BeginGroup(totalRect_browserSpace); widget(); pages(); GUI.EndGroup(); if (totalRect_browserSpace.IsHovered()) if (curEvent.isMouseUp || curEvent.isMouseDrag || curEvent.isScroll) // prevents these events from reaching original gui curEvent.Use(); originalBrowserGUI(); } void originalBrowserGUI() { if (origBrowserOnGUIDelegate.GetMethodInfo().DeclaringType.Name.Contains("VTabs")) return; if (isOneColumn && currentOpacity.Approx(1)) // to optimize locked one-column browser functioning as a dedicated favorites window if (originalGUICalledOnce) // needs to be called once to init stuff so pinging object won't throw exceptions return; if (origBrowserOnGUIDelegate.GetMethodInfo().IsStatic) // wrapped by vFolders origBrowserOnGUIDelegate.GetMethodInfo().Invoke(null, new[] { wrappedBrowser }); else origBrowserOnGUIDelegate.GetMethodInfo().Invoke(wrappedBrowser, null); originalGUICalledOnce = true; } createData(); closeNavbarSearch(); UpdateMouseState(); UpdateDragging(); var doOriginalGUIFirst = VFavoritesMenu.pageScrollEnabled ? curEvent.isRepaint || curEvent.isLayout : curEvent.isRepaint; if (renamingPage) doOriginalGUIFirst = !curEvent.isMouseDown && !curEvent.isMouseUp; if (renamingPage && curEvent.isMouseDown) curEvent.Use(); if (doOriginalGUIFirst) doOriginalGUIFirst_(); else doVFavoritesGUIFirst_(); if (isWrappedBrowserLocked) if (!totalRect_browserSpace.IsHovered() && !animatingDroppedItem && !animatingPageScroll) return; if (animatingOpacity || animatingDroppedItem || animatingPageScroll || animatingRowGaps) wrappedBrowser.Repaint(); } static void SelectItem(Item item) { void openFolder(EditorWindow browser, string path) { var folderAsset = AssetDatabase.LoadAssetAtPath(path); if (browser.GetFieldValue("m_ViewMode") == 1) browser.InvokeMethod("SetFolderSelection", new[] { folderAsset.GetInstanceID() }, false); else { Selection.activeObject = folderAsset; browser.GetMemberValue("m_AssetTree")?.GetPropertyValue("data")?.InvokeMethod("SetExpanded", folderAsset.GetInstanceID(), true); } } void selectSceneObject() { if (!item.isSceneGameObject) return; Selection.activeObject = item.obj; } void selectAsset_twoColumns() { if (!item.isAsset) return; if (wrappedBrowser.GetFieldValue("m_ViewMode") != 1) return; openFolder(wrappedBrowser, item.assetPath.GetParentPath()); Selection.activeObject = item.obj; } void selectAsset_oneColumn() { if (!item.isAsset) return; if (wrappedBrowser.GetFieldValue("m_ViewMode") != 0) return; Selection.activeObject = item.obj; } void openFolder_unlocked() { if (!item.isFolder) return; if (isWrappedBrowserLocked) return; openFolder(wrappedBrowser, item.assetPath); } void openFolder_locked() { if (!item.isFolder) return; if (!isWrappedBrowserLocked) return; var unlockedBrowser = allBrowsers.FirstOrDefault(r => !r.GetMemberValue("isLocked")); var browserToUse = isOneColumn ? unlockedBrowser : lockedBrowser; if (!browserToUse) return; openFolder(browserToUse, item.assetPath); } selectSceneObject(); selectAsset_twoColumns(); selectAsset_oneColumn(); openFolder_unlocked(); openFolder_locked(); item.lastSelectTime_ticks = data.curPage.lastItemSelectTime_ticks = System.DateTime.UtcNow.Ticks; } static void OpenItem(Item item) { void openText() { if (item.assetPath.GetExtension() != ".cs" && item.assetPath.GetExtension() != ".shader" && item.assetPath.GetExtension() != ".compute" && item.assetPath.GetExtension() != ".cginc" && item.assetPath.GetExtension() != ".json") return; AssetDatabase.OpenAsset(item.globalId.guid.LoadGuid()); } void openPrefab() { if (item.type != typeof(GameObject)) return; if (!item.isLoadable) return; if ((item.obj as GameObject).scene.rootCount != 0) return; AssetDatabase.OpenAsset(item.obj); } void openScene() { if (item.type != typeof(SceneAsset)) return; EditorSceneManager.SaveOpenScenes(); EditorSceneManager.OpenScene(item.assetPath); } void openSceneForNotLoadedGameObject() { if (!item.isSceneGameObject) return; if (item.isLoadable) return; if (item.isDeleted) return; EditorSceneManager.SaveOpenScenes(); EditorSceneManager.OpenScene(item.assetPath); Selection.activeObject = item.obj; } openText(); openPrefab(); openScene(); openSceneForNotLoadedGameObject(); } static float rowHeight => 44 * data.rowScale; static float crossButtonOffsetFromRight = 23; static float crossButtonSize = 16; static Rect totalRect_browserSpace { get { if (isOneColumn) return wrappedBrowser.position.SetPos(0, 0); var treeViewRect = wrappedBrowser.GetFieldValue("m_TreeViewRect"); return wrappedBrowser.position.SetPos(0, 0).SetWidth(treeViewRect.width).SetHeightFromBottom(treeViewRect.height); } } static Rect totalRect_groupSpace => totalRect_browserSpace.SetPos(0, 0); static Rect widgetRect_browserSpace => widgetRect_groupSpace.MoveY(totalRect_browserSpace.y); static Rect widgetRect_groupSpace; static bool renamingPage; static string prevPageName; static int lastNumberKeyPressedIndex; static long lastNumberKeyPressedTime_ticks; static bool isOneColumn => wrappedBrowser?.GetFieldValue("m_ViewMode") == 0; static bool skipNextRepaint; static bool originalGUICalledOnce; static void UpdateMouseState() // called from WrappedOnGUI { if (!shortcutPressed && !isWrappedBrowserLocked) { setDefaultState(); return; } if (!totalRect_browserSpace.IsHovered()) { setDefaultState(); return; } void setDefaultState() { mousePressed = false; mousePressedOnCrossButtonArea = false; mousePressedOnWidget = false; pressedItem = null; doubleclickUnhandled = false; } void position() { mousePosiion_browserSpace = curEvent.mousePosition; } void down() { if (!curEvent.isMouseDown) return; mousePressed = true; mousePressedOnCrossButtonArea = totalRect_browserSpace.SetWidthFromRight(0).MoveX(-crossButtonOffsetFromRight).SetWidthFromMid(crossButtonSize).IsHovered(); mousePressedOnWidget = curEvent.mousePosition.y >= widgetRect_browserSpace.y; mouseDownPosiion_browserSpace = curEvent.mousePosition; var pressedItemIndex = (mouseDownPosition_rowsSpace.y / rowHeight).FloorToInt(); if (pressedItemIndex.IsInRangeOf(data.curPage.items) && !mousePressedOnCrossButtonArea && !mousePressedOnWidget) pressedItem = data.curPage.items[pressedItemIndex]; doubleclickUnhandled = !mousePressedOnCrossButtonArea && curEvent.clickCount == 2; curEvent.Use(); } void up() { if (!curEvent.isMouseUp) return; mousePressed = false; doubleclickUnhandled = false; pressedItem = null; } position(); down(); up(); } static bool mousePressed; static bool mousePresesdOnItem => pressedItem != null; static bool mousePressedOnCrossButtonArea; static bool mousePressedOnWidget; static bool doubleclickUnhandled; static float groupRectOffsetY => isOneColumn ? 0 : -20; static Vector2 mousePosiion_browserSpace; static Vector2 mousePosition_groupSpace => mousePosiion_browserSpace.AddY(groupRectOffsetY); static Vector2 mousePosition_rowsSpace => mousePosiion_browserSpace.AddY(groupRectOffsetY + data.curPage.scrollPos); static Vector2 mouseDownPosiion_browserSpace; static Vector2 mouseDownPosition_groupSpace => mouseDownPosiion_browserSpace.AddY(groupRectOffsetY); static Vector2 mouseDownPosition_rowsSpace => mouseDownPosiion_browserSpace.AddY(groupRectOffsetY + data.curPage.scrollPos); static float mouseDragDistance => (mousePosiion_browserSpace - mouseDownPosiion_browserSpace).magnitude; static Item pressedItem; static void UpdateAnimations() // Update { void calcDeltaTime() { deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); if (deltaTime > .05f) deltaTime = .0166f; lastLayoutTime = EditorApplication.timeSinceStartup; } void opacity() { if (!VFavoritesMenu.fadeAnimationsEnabled) { currentOpacity = targetOpacity; return; } if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive) { currentOpacity = targetOpacity; return; } SmoothDamp(ref currentOpacity, targetOpacity, 11, ref currentOpacityDerivative, deltaTime); if (targetOpacity == 0 && currentOpacity < .04f) currentOpacity = 0; } void pagesScroll() { if (!data) return; if (!VFavoritesMenu.pageScrollAnimationEnabled) { pagesScrollPos = data.curPageIndex; return; } if (pagesScrollPos == -1) pagesScrollPos = data.curPageIndex; SmoothDamp(ref pagesScrollPos, data.curPageIndex, 5, ref pagesScrollDerivative, deltaTime); if (pagesScrollPos.DistanceTo(data.curPageIndex) < .001f) pagesScrollPos = data.curPageIndex; } void rowGaps() { if (!data) return; var lerpSpeed = 10; for (int i = 0; i < data.curPage.rowGaps.Count; i++) data.curPage.rowGaps[i] = Lerp(data.curPage.rowGaps[i], draggingItem && i == insertDraggedItemAtIndex ? rowHeight : 0, lerpSpeed, deltaTime); } void droppedItem() { if (!animatingDroppedItem) return; var yLerpSpeed = 8; var shadowLerpSpeed = 8; var highlightLerpSpeed = 10; SmoothDamp(ref droppedItemY_rowsSpace, data.curPage.items.IndexOf(VFavorites.droppedItem) * rowHeight, yLerpSpeed, ref droppedItemYDerivative, deltaTime); Lerp(ref droppedItemShadowAmount, 0, shadowLerpSpeed, deltaTime); Lerp(ref droppedItemHighlightAmount, 0, highlightLerpSpeed, deltaTime); if (droppedItemShadowAmount < .01f) animatingDroppedItem = false; } void pageButtons() { if (!VFavoritesMenu.pageScrollAnimationEnabled) { prevPageButtonBrightness = nextPageButtonBrightness = 1; return; } var lerpSpeed = 7; Lerp(ref prevPageButtonBrightness, 1, lerpSpeed, deltaTime); Lerp(ref nextPageButtonBrightness, 1, lerpSpeed, deltaTime); } calcDeltaTime(); opacity(); pagesScroll(); rowGaps(); droppedItem(); pageButtons(); } static void CancelRowAnimations() { for (int i = 0; i < data.curPage.rowGaps.Count; i++) data.curPage.rowGaps[i] = 0; animatingDroppedItem = false; droppedItem = null; } static float deltaTime; static double lastLayoutTime; static bool animatingRowGaps => data.curPage.rowGaps.Any(r => r > .1f && r < rowHeight - .1f); static float pagesScrollPos = -1; static float pagesScrollDerivative; static bool animatingPageScroll => !pagesScrollPos.Approx(data ? data.curPageIndex : 0); static float droppedItemY_rowsSpace; static float droppedItemYDerivative; static float droppedItemShadowAmount; static float droppedItemHighlightAmount; static bool animatingDroppedItem; static float prevPageButtonBrightness = 1; static float nextPageButtonBrightness = 1; static float currentOpacity; static float currentOpacityDerivative; static float targetOpacity => curEvent.holdingAlt || renamingPage || draggingItemFromPageToOutside || isWrappedBrowserLocked ? 1 : 0; // holdingAlt instead of shortcutPressed to prevent unwrapping due to incorrect event modifiers on key down on mac static bool animatingOpacity => currentOpacity.DistanceTo(targetOpacity) > .01f; static void UpdateDragging() // called from WrappedOnGUI { void initFromOutside() { if (draggingItem) return; if (!totalRect_browserSpace.IsHovered()) return; if (!curEvent.isDragUpdate) return; if (!DragAndDrop.objectReferences.FirstOrDefault()) return; if (draggingItemFromPageToOutside) return; // to avoid duplication when dragging item from page to outside and back animatingDroppedItem = false; draggingItem = true; draggingItemFromPage = false; draggedItem = new Item(DragAndDrop.objectReferences.FirstOrDefault()); draggedItemHoldOffset = 0; data.curPage.lastItemDragTime_ticks = System.DateTime.UtcNow.Ticks; } void initFromPage() { if (draggingItem) return; if (!totalRect_browserSpace.IsHovered()) return; if (!curEvent.isMouseDrag) return; if (mouseDragDistance < 2) return; var i = (mouseDownPosition_rowsSpace.y / rowHeight).FloorToInt(); if (i >= data.curPage.items.Count) return; if (i < 0) return; // somehow i = -1 with mouseDownPosition_rowsSpace.y = -20 when dragging in from outside quickly animatingDroppedItem = false; draggingItem = true; draggingItemFromPage = true; draggingItemFromPageAtIndex = i; draggedItem = data.curPage.items[i]; draggedItemHoldOffset = (i * rowHeight + rowHeight / 2) - mouseDownPosition_rowsSpace.y; data.curPage.lastItemDragTime_ticks = System.DateTime.UtcNow.Ticks; data.curPage.items.Remove(draggedItem); data.curPage.rowGaps[draggingItemFromPageAtIndex] = rowHeight; data.Dirty(); data.Save(); } void acceptFromOutside() { if (!draggingItem) return; if (!curEvent.isDragPerform) return; DragAndDrop.AcceptDrag(); curEvent.Use(); AcceptDragging(); } void acceptFromPage() { if (!draggingItem) return; if (!curEvent.isMouseUp) return; curEvent.Use(); AcceptDragging(); } void cancelFromOutside() { if (!draggingItemFromOutside) return; if (totalRect_browserSpace.IsHovered()) return; CancelDragging(); } void cancelFromPageAndInitToOutside() { if (!curEvent.isMouseDrag) return; if (!draggingItemFromPage) return; if (totalRect_browserSpace.IsHovered()) return; if (DragAndDrop.objectReferences.Any()) return; DragAndDrop.PrepareStartDrag(); DragAndDrop.objectReferences = new[] { draggedItem.obj }; DragAndDrop.StartDrag(draggedItem.name); CancelDragging(); draggingItemFromPageToOutside = true; } void setVisualMode() { if (!draggingItem) return; DragAndDrop.visualMode = DragAndDropVisualMode.Generic; } void setHotControl() { if (!draggingItem) return; EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); } void resetDraggingItemFromPageToOutside() // delayCall loop { if (!DragAndDrop.objectReferences.Any()) draggingItemFromPageToOutside = false; EditorApplication.delayCall -= resetDraggingItemFromPageToOutside; EditorApplication.delayCall += resetDraggingItemFromPageToOutside; // DragAndDrop.objectReferences is unreliable outside of delayCall } initFromOutside(); initFromPage(); acceptFromOutside(); acceptFromPage(); cancelFromOutside(); cancelFromPageAndInitToOutside(); setVisualMode(); setHotControl(); EditorApplication.delayCall -= resetDraggingItemFromPageToOutside; EditorApplication.delayCall += resetDraggingItemFromPageToOutside; } static void AcceptDragging() { draggingItem = false; draggingItemFromPage = false; mousePressed = false; data.curPage.items.AddAt(draggedItem, insertDraggedItemAtIndex); data.curPage.rowGaps[insertDraggedItemAtIndex] -= rowHeight; data.curPage.rowGaps.AddAt(0, insertDraggedItemAtIndex); droppedItem = draggedItem; droppedItemY_rowsSpace = draggedItemY_groupSpace + data.curPage.scrollPos; droppedItemYDerivative = 0; droppedItemShadowAmount = droppedItemHighlightAmount = 1; animatingDroppedItem = true; draggedItem = null; EditorGUIUtility.hotControl = 0; data.Dirty(); data.Save(); } static void CancelDragging() { if (!draggingItem) return; draggingItem = false; mousePressed = false; if (!draggingItemFromPage) { draggedItem = null; return; } data.curPage.items.AddAt(draggedItem, draggingItemFromPageAtIndex); data.curPage.rowGaps[draggingItemFromPageAtIndex] -= rowHeight; droppedItem = draggedItem; droppedItemY_rowsSpace = draggedItemY_groupSpace - data.curPage.scrollPos; droppedItemShadowAmount = droppedItemHighlightAmount = 1; animatingDroppedItem = true; draggingItemFromPage = false; draggedItem = null; EditorGUIUtility.hotControl = 0; data.Dirty(); data.Save(); } static bool draggingItem; static bool draggingItemFromPage; static bool draggingItemFromPageToOutside; static bool draggingItemFromOutside => draggingItem && !draggingItemFromPage; static int draggingItemFromPageAtIndex; static Item draggedItem; static float draggedItemHoldOffset; static float draggedItemY_groupSpace => (mousePosition_groupSpace.y - rowHeight / 2 + draggedItemHoldOffset).Clamp(0, 12321); static float draggedItemY_rowsSpace => draggedItemY_groupSpace + data.curPage.scrollPos; static int insertDraggedItemAtIndex => ((mousePosition_rowsSpace.y + draggedItemHoldOffset) / rowHeight).FloorToInt().Clamp(0, data.curPage.items.Count); static Item droppedItem; static void UpdateGUIWrapping() // called from EditorApplicaton.update { void wrap() { if (wrappedBrowser) return; if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive) return; if (!shortcutPressed) return; if (EditorWindow.mouseOverWindow?.GetType() != t_BrowserWindow) return; if (t_VTabs != null && !EditorPrefsCached.GetBool("vTabs-pluginDisabled", false) && EditorWindow.mouseOverWindow.GetMemberValue("isLocked")) return; WrapBrowserGUI(EditorWindow.mouseOverWindow); wrappedBrowser = EditorWindow.mouseOverWindow; wrappedBrowser.Focus(); wrappedBrowser.Repaint(); t_BrowserWindow.SetFieldValue("s_LastInteractedProjectBrowser", wrappedBrowser); // so vTabs can copy its layout setting } void unwrap() { if (!wrappedBrowser) return; if (shortcutPressed && wrappedBrowser.hasFocus) return; if (currentOpacity > 0 && wrappedBrowser.hasFocus) return; CancelDragging(); CancelRowAnimations(); UnwrapBrowserGUI(); wrappedBrowser.Repaint(); wrappedBrowser = null; } unwrap(); wrap(); } static void WrapBrowserGUI(EditorWindow browser) { var hostView = fi_m_Parent.GetValue(browser); var newDelegate = mi_WrappedOnGUI.CreateDelegate(t_EditorWindowDelegate, hostView); origBrowserOnGUIDelegate = fi_m_OnGUI.GetValue(hostView) as System.Delegate; fi_m_OnGUI.SetValue(hostView, newDelegate); } static void UnwrapBrowserGUI() { if (wrappedBrowser.GetFieldValue("m_Parent").GetFieldValue("m_OnGUI").Method != mi_WrappedOnGUI) return; wrappedBrowser.GetFieldValue("m_Parent").SetFieldValue("m_OnGUI", origBrowserOnGUIDelegate); } static EditorWindow wrappedBrowser; static System.Delegate origBrowserOnGUIDelegate; static bool shortcutPressed { get { if (VFavoritesMenu.activeOnAltEnabled) return curEvent.holdingAlt; if (VFavoritesMenu.activeOnAltShiftEnabled) return curEvent.modifiers == (EventModifiers.Alt | EventModifiers.Shift); if (VFavoritesMenu.activeOnCtrlAltEnabled) if (Application.platform == RuntimePlatform.OSXEditor) return curEvent.modifiers == (EventModifiers.Command | EventModifiers.Alt); else return curEvent.modifiers == (EventModifiers.Control | EventModifiers.Alt); return false; } } static void UpdateLocking() // called from EditorApplicaton.update { void unsetWrappedBrowser() { if (!isWrappedBrowserLocked) return; if (wrappedBrowser.GetFieldValue("m_Parent").GetFieldValue("m_OnGUI").Method == mi_WrappedOnGUI) return; wrappedBrowser = null; // nulls wrappedBrowser if it was unwrapped externally // ie when locked browser gets moved or maximized } void markLockedBrowser() { if (!lockedBrowser) return; MarkAsLocked(lockedBrowser); // fixes marking getting reset on scene load } void setMinWidthOnLockedBrowser() { if (!lockedBrowser) return; if (lockedBrowser.minSize.x == 100) return; lockedBrowser.minSize = Vector2.one * 100; } void lock_() { if (lockedBrowser) return; if (!wrappedBrowser) return; if (!wrappedBrowser.GetMemberValue("isLocked")) return; lockedBrowser = wrappedBrowser; EditorPrefsCached.SetInt("vFavorites-lockedBrowserHash", lockedBrowser.GetHashCode()); EditorPrefsCached.SetInt("vFavorites-lockedBrowserDockAreaInstanceId", lockedBrowser.GetMemberValue("m_Parent").GetInstanceID()); curEvent.Use(); } void unlock() { if (!lockedBrowser) return; if (lockedBrowser.GetMemberValue("isLocked")) return; lockedBrowser = null; EditorPrefsCached.SetInt("vFavorites-lockedBrowserHash", 0); EditorPrefsCached.SetInt("vFavorites-lockedBrowserDockAreaInstanceId", 0); curEvent.Use(); } void wrap() { if (!lockedBrowser) return; if (!lockedBrowser.hasFocus) return; if (isWrappedBrowserLocked) return; WrapBrowserGUI(lockedBrowser); currentOpacity = 1; wrappedBrowser = lockedBrowser; } unsetWrappedBrowser(); markLockedBrowser(); setMinWidthOnLockedBrowser(); lock_(); unlock(); wrap(); } static EditorWindow lockedBrowser { get { if (_lockedBrowser) return _lockedBrowser; var lockedBrowserInstanceId = EditorPrefsCached.GetInt("vFavorites-lockedBrowserInstanceId", 0); if (lockedBrowserInstanceId == 0) return null; var window = Resources.InstanceIDToObject(lockedBrowserInstanceId) as EditorWindow; if (window && window.GetType() == t_BrowserWindow) // prevents iid collisions _lockedBrowser = window; if (_lockedBrowser) return _lockedBrowser; _lockedBrowser = allBrowsers.FirstOrDefault(r => IsMarkedAsLocked(r)); if (_lockedBrowser) return _lockedBrowser; // todo set instanceid // EditorPrefsCached.SetInt("vFavorites-lockedBrowserInstanceId", 0); // one attempt to find the locked browser isn't enough after unmaximize return null; } set { if (_lockedBrowser) MarkAsUnlocked(_lockedBrowser); if (value == null) { EditorPrefsCached.SetInt("vFavorites-lockedBrowserInstanceId", 0); _lockedBrowser = null; } else { EditorPrefsCached.SetInt("vFavorites-lockedBrowserInstanceId", value.GetInstanceID()); MarkAsLocked(value); _lockedBrowser = value; } } } static EditorWindow _lockedBrowser; static bool IsMarkedAsLocked(EditorWindow browser) => browser.GetMemberValue("m_SearchFilter")?.GetMemberValue("m_OriginalText") == "asd"; static void MarkAsLocked(EditorWindow browser) => browser.GetMemberValue("m_SearchFilter")?.SetMemberValue("m_OriginalText", "asd"); static void MarkAsUnlocked(EditorWindow browser) => browser.GetMemberValue("m_SearchFilter")?.SetMemberValue("m_OriginalText", ""); static bool isWrappedBrowserLocked => wrappedBrowser && wrappedBrowser == lockedBrowser; static bool CanBrowserBeWrapped_byVTabs(EditorWindow browser) => !IsMarkedAsLocked(browser); static void RepaintOnAltUp() // Update { var lastEvent = typeof(Event).GetFieldValue("s_Current"); if (wasAlt && !lastEvent.alt) if (EditorWindow.mouseOverWindow?.GetType() == t_BrowserWindow) EditorApplication.RepaintProjectWindow(); wasAlt = lastEvent.alt; } static bool wasAlt; [InitializeOnLoadMethod] static void Init() { if (VFavoritesMenu.pluginDisabled) return; void subscribe() { EditorApplication.update -= UpdateGUIWrapping; EditorApplication.update += UpdateGUIWrapping; EditorApplication.update -= UpdateLocking; EditorApplication.update += UpdateLocking; EditorApplication.update -= UpdateAnimations; EditorApplication.update += UpdateAnimations; EditorApplication.update -= RepaintOnAltUp; EditorApplication.update += RepaintOnAltUp; EditorApplication.quitting -= VFavoritesState.Save; EditorApplication.quitting += VFavoritesState.Save; } void loadData() { data = AssetDatabase.LoadAssetAtPath(EditorPrefsCached.GetString("vFavorites-lastKnownDataPath-" + GetProjectId())); if (data) return; data = AssetDatabase.FindAssets("t:VFavoritesData").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); if (!data) return; EditorPrefsCached.SetString("vFavorites-lastKnownDataPath-" + GetProjectId(), data.GetPath()); } void loadDataDelayed() { if (data) return; EditorApplication.delayCall += () => EditorApplication.delayCall += loadData; // AssetDatabase isn't up to date at this point (it gets updated after InitializeOnLoadMethod) // and if current AssetDatabase state doesn't contain the data - it won't be loaded during Init() // so here we schedule an additional, delayed attempt to load the data // this addresses reports of data loss when trying to load it on a new machine } subscribe(); loadData(); loadDataDelayed(); } public static VFavoritesData data; static void BeforeWindowCreated_byVTabs(object dockArea) { if (!wrappedBrowser) return; if (wrappedBrowser.GetFieldValue("m_Parent") != dockArea) return; CancelDragging(); CancelRowAnimations(); UnwrapBrowserGUI(); draggingItemFromPageToOutside = false; } static IEnumerable allBrowsers => _allBrowsers ??= t_BrowserWindow.GetFieldValue("s_ProjectBrowsers").Cast(); static IEnumerable _allBrowsers; static Type t_BrowserWindow = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); static Type t_HostView = typeof(Editor).Assembly.GetType("UnityEditor.HostView"); static Type t_EditorWindowDelegate = t_HostView.GetNestedType("EditorWindowDelegate", maxBindingFlags); static FieldInfo fi_m_Parent = typeof(EditorWindow).GetField("m_Parent", maxBindingFlags); static FieldInfo fi_m_OnGUI = t_HostView.GetField("m_OnGUI", maxBindingFlags); static MethodInfo mi_WrappedOnGUI = typeof(VFavorites).GetMethod(nameof(WrappedOnGUI), maxBindingFlags); static Type t_VHierarchy = Type.GetType("VHierarchy.VHierarchy") ?? Type.GetType("VHierarchy.VHierarchy, VHierarchy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); static Type t_VFolders = Type.GetType("VFolders.VFolders") ?? Type.GetType("VFolders.VFolders, VFolders, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); static Type t_VTabs = Type.GetType("VTabs.VTabs") ?? Type.GetType("VTabs.VTabs, VTabs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); static MethodInfo mi_VHierarchy_GetIconName = t_VHierarchy?.GetMethod("GetIconName_forVFavorites", maxBindingFlags); static MethodInfo mi_VFolders_DrawBigFolderIcon = t_VFolders?.GetMethod("DrawBigFolderIcon_forVFavorites", maxBindingFlags); const string version = "2.0.9"; } } #endif