#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 Type = System.Type; using static VHierarchy.VHierarchyData; using static VHierarchy.VHierarchyPalette; using static VHierarchy.Libs.VUtils; using static VHierarchy.Libs.VGUI; namespace VHierarchy { public class VHierarchyPaletteWindow : EditorWindow { void OnGUI() { if (!palette) { Close(); return; } int hoveredColorIndex = -1; string hoveredIconNameOrGuid = null; void background() { position.SetPos(0, 0).Draw(windowBackground); } void outline() { if (Application.platform == RuntimePlatform.OSXEditor) return; position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); } void colors() { if (!palette.colorsEnabled) { Space(-spaceAfterColors); return; } var rowRect = ExpandWidthLabelRect(height: cellSize).SetX(paddingX); void color(int i) { var cellRect = rowRect.MoveX(i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); void backgroundSelected() { if (!initialColorIndexes.Contains(i)) return; cellRect.Resize(1).DrawWithRoundedCorners(selectedBackground, 2); } void backgroundHovered() { if (!cellRect.IsHovered()) return; cellRect.Resize(1).DrawWithRoundedCorners(this.hoveredBackground, 2); } void crossIcon() { if (i != 0) return; SetLabelAlignmentCenter(); GUI.Label(cellRect.SetSizeFromMid(iconSize), EditorGUIUtility.IconContent("CrossIcon")); ResetLabelStyle(); } void color() { if (i == 0) return; var brightness = i <= VHierarchyPalette.greyColorsCount ? 1.02f : 1.35f; var outlineColor = i <= VHierarchyPalette.greyColorsCount ? Greyscale(.0f, .4f) : Greyscale(.15f, .2f); cellRect.Resize(3).DrawWithRoundedCorners(outlineColor, 4); cellRect.Resize(4).DrawWithRoundedCorners((palette.colors[i - 1] * brightness).SetAlpha(1), 3); cellRect.Resize(4).AddWidthFromRight(-2).DrawCurtainLeft(GUIColors.windowBackground.SetAlpha((1 - palette.colors[i - 1].a) * .45f)); } void setHovered() { if (!cellRect.IsHovered()) return; hoveredColorIndex = i; } void closeOnClick() { if (!cellRect.IsHovered()) return; if (!curEvent.isMouseDown) return; Close(); } cellRect.MarkInteractive(); backgroundSelected(); backgroundHovered(); crossIcon(); color(); setHovered(); closeOnClick(); } for (int i = 0; i < palette.colors.Count + 1; i++) color(i); } void icons() { void row(IconRow iconRow) { if (!iconRow.enabled) return; if (iconRow.isEmpty) return; var rowRect = ExpandWidthLabelRect(height: cellSize).SetX(paddingX); var isFirstEnabledRow = palette.iconRows.First(r => r.enabled) == iconRow; void icon(int i) { var cellRect = rowRect.MoveX(i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); var isCrossIcon = isFirstEnabledRow && i == 0; var actualIconIndex = isFirstEnabledRow ? i - 1 : i; var isBuiltinIcon = !isCrossIcon && actualIconIndex < iconRow.builtinIcons.Count; var isCustomIcon = !isCrossIcon && actualIconIndex >= iconRow.builtinIcons.Count; var iconNameOrGuid = isCrossIcon ? "" : isCustomIcon ? iconRow.customIcons[actualIconIndex - iconRow.builtinIcons.Count] : iconRow.builtinIcons[actualIconIndex]; void backgroundSelected() { if (!initialIconNamesOrGuids.Contains(iconNameOrGuid)) return; cellRect.Resize(1).DrawWithRoundedCorners(selectedBackground, 2); } void backgroundHovered() { if (!cellRect.IsHovered()) return; cellRect.Resize(1).DrawWithRoundedCorners(this.hoveredBackground, 2); } void crossIcon() { if (!isCrossIcon) return; SetLabelAlignmentCenter(); GUI.Label(cellRect.SetSizeFromMid(iconSize), EditorGUIUtility.IconContent("CrossIcon")); ResetLabelStyle(); } void builtinIcon() { if (!isBuiltinIcon) return; SetLabelAlignmentCenter(); GUI.Label(cellRect.SetSizeFromMid(iconSize), EditorGUIUtility.IconContent(iconNameOrGuid)); ResetLabelStyle(); } void customIcon() { if (!isCustomIcon) return; var texture = AssetDatabase.LoadAssetAtPath(iconNameOrGuid.ToPath()); GUI.DrawTexture(cellRect.SetSizeFromMid(iconSize), texture ?? Texture2D.blackTexture); } void setHovered() { if (!cellRect.IsHovered()) return; hoveredIconNameOrGuid = iconNameOrGuid; } void closeOnClick() { if (!cellRect.IsHovered()) return; if (!curEvent.isMouseDown) return; Close(); } cellRect.MarkInteractive(); backgroundSelected(); backgroundHovered(); crossIcon(); builtinIcon(); customIcon(); setHovered(); closeOnClick(); } for (int i = 0; i < iconRow.iconCount + (isFirstEnabledRow ? 1 : 0); i++) icon(i); Space(rowSpacing - 2); } for (int i = 0; i < palette.iconRows.Count; i++) row(palette.iconRows[i]); } void setColorsAndIcons() { if (!curEvent.isRepaint) return; if (palette.iconRows.Any(r => r.enabled)) if (hoveredIconNameOrGuid != null) SetIcon(hoveredIconNameOrGuid); else SetInitialIcons(); if (palette.colorsEnabled) if (hoveredColorIndex != -1) SetColor(hoveredColorIndex); else SetInitialColors(); } void updatePosition() { if (!curEvent.isLayout) return; void calcDeltaTime() { deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); if (deltaTime > .05f) deltaTime = .0166f; lastLayoutTime = EditorApplication.timeSinceStartup; } void resetCurPos() { if (currentPosition != default) return; currentPosition = position.position; // position.position is always int, which can't be used for lerping } void lerpCurPos() { var speed = 9; SmoothDamp(ref currentPosition, targetPosition, speed, ref positionDeriv, deltaTime); // Lerp(ref currentPosition, targetPosition, speed, deltaTime); } void setCurPos() { position = position.SetPos(currentPosition); } calcDeltaTime(); resetCurPos(); lerpCurPos(); setCurPos(); if (!currentPosition.y.Approx(targetPosition.y)) Repaint(); } void closeOnEscape() { if (!curEvent.isKeyDown) return; if (curEvent.keyCode != KeyCode.Escape) return; SetInitialColors(); SetInitialIcons(); Close(); } RecordUndoOnDatas(); background(); outline(); Space(paddingY); colors(); Space(spaceAfterColors); icons(); setColorsAndIcons(); updatePosition(); closeOnEscape(); EditorApplication.RepaintHierarchyWindow(); } static float iconSize => 18; static float iconSpacing => 2; static float cellSize => iconSize + iconSpacing; static float spaceAfterColors => 11; public float rowSpacing = 1; static float paddingX => 12; static float paddingY => 12; Color windowBackground => isDarkTheme ? Greyscale(.23f) : Greyscale(.7f); Color selectedBackground => isDarkTheme ? new Color(.3f, .5f, .7f, .8f) : new Color(.3f, .5f, .7f, .4f) * 1.35f; Color hoveredBackground = Greyscale(1, .3f); public Vector2 targetPosition; public Vector2 currentPosition; Vector2 positionDeriv; float deltaTime; double lastLayoutTime; void SetIcon(string iconNameOrGuid) { foreach (var r in goDatas) r.iconNameOrGuid = iconNameOrGuid; } void SetColor(int colorIndex) { foreach (var r in goDatas) r.colorIndex = colorIndex; } void SetInitialIcons() { for (int i = 0; i < goDatas.Count; i++) goDatas[i].iconNameOrGuid = initialIconNamesOrGuids[i]; } void SetInitialColors() { for (int i = 0; i < goDatas.Count; i++) goDatas[i].colorIndex = initialColorIndexes[i]; } void RemoveEmptyGoDatas() { var toRemove = goDatas.Where(r => r.iconNameOrGuid == "" && r.colorIndex == 0); foreach (var goData in toRemove) goData.sceneData.goDatas_byGlobalId.RemoveValue(goData); if (toRemove.Any()) Undo.CollapseUndoOperations(Undo.GetCurrentGroup() - 1); } void RecordUndoOnDatas() { if (usingDataSO) if (data) data.RecordUndo(); foreach (var r in usedDataComponents) r.RecordUndo(); } void MarkDatasDirty() { if (usingDataSO) if (data) data.Dirty(); foreach (var r in usedDataComponents) r.Dirty(); } void SaveData() { if (usingDataSO) data.Save(); } bool usingDataSO => !gameObjects.Select(r => r.scene).All(r => VHierarchy.dataComponents_byScene.GetValueOrDefault(r) != null); IEnumerable usedDataComponents => VHierarchy.dataComponents_byScene.Where(kvp => kvp.Value && gameObjects.Select(r => r.scene).Contains(kvp.Key)).Select(kvp => kvp.Value); void OnLostFocus() { if (curEvent.holdingAlt && EditorWindow.focusedWindow?.GetType().Name == "SceneHierarchyWindow") CloseNextFrameIfNotRefocused(); else Close(); } void CloseNextFrameIfNotRefocused() { EditorApplication.delayCall += () => { if (EditorWindow.focusedWindow != this) Close(); }; } public void Init(List gameObjects) { void createData() { if (VHierarchy.data) return; VHierarchy.data = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(VHierarchy.data, GetScriptPath("VHierarchy").GetParentPath().CombinePath("vHierarchy Data.asset")); } void createPalette() { if (VHierarchy.palette) return; VHierarchy.palette = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(VHierarchy.palette, GetScriptPath("VHierarchy").GetParentPath().CombinePath("vHierarchy Palette.asset")); } void setSize() { var rowCellCounts = new List(); if (palette.colorsEnabled) rowCellCounts.Add(palette.colors.Count + 1); foreach (var r in palette.iconRows.Where(r => r.enabled)) rowCellCounts.Add(r.iconCount + (r == palette.iconRows.First(r => r.enabled) ? 1 : 0)); var width = rowCellCounts.Max() * cellSize + paddingX * 2; var iconRowCount = palette.iconRows.Count(r => r.enabled && !r.isEmpty); var rowCount = iconRowCount + (palette.colorsEnabled ? 1 : 0); var height = rowCount * (cellSize + rowSpacing) + (palette.colorsEnabled && palette.iconRows.Any(r => r.enabled && !r.isEmpty) ? spaceAfterColors : 0) + paddingY * 2; position = position.SetSize(width, height).SetPos(targetPosition); } void getDatas() { goDatas.Clear(); foreach (var r in gameObjects) goDatas.Add(VHierarchy.GetGameObjectData(r, createDataIfDoesntExist: true)); } void getInitColorsAndIcons() { initialColorIndexes.Clear(); initialIconNamesOrGuids.Clear(); foreach (var r in goDatas) initialColorIndexes.Add(r.colorIndex); foreach (var r in goDatas) initialIconNamesOrGuids.Add(r.iconNameOrGuid); } this.gameObjects = gameObjects; RecordUndoOnDatas(); createData(); createPalette(); setSize(); getDatas(); getInitColorsAndIcons(); } void OnDestroy() { RemoveEmptyGoDatas(); MarkDatasDirty(); SaveData(); } public List gameObjects = new List(); public List goDatas = new List(); public List initialColorIndexes = new List(); public List initialIconNamesOrGuids = new List(); static VHierarchyPalette palette => VHierarchy.palette; static VHierarchyData data => VHierarchy.data; public static void CreateInstance(Vector2 position) { instance = ScriptableObject.CreateInstance(); instance.ShowPopup(); instance.position = instance.position.SetPos(position).SetSize(200, 300); instance.targetPosition = position; } public static VHierarchyPaletteWindow instance; } } #endif