diff --git a/Docs.meta b/Docs.meta new file mode 100644 index 0000000..c206131 --- /dev/null +++ b/Docs.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b64cf2375bca5ea46926249756dca60c +folderAsset: yes +timeCreated: 1497243946 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Docs/QuickToggle-Show.png b/Docs/QuickToggle-Show.png new file mode 100644 index 0000000..3929f59 Binary files /dev/null and b/Docs/QuickToggle-Show.png differ diff --git a/Docs/QuickToggle-Show.png.meta b/Docs/QuickToggle-Show.png.meta new file mode 100644 index 0000000..d9caeff --- /dev/null +++ b/Docs/QuickToggle-Show.png.meta @@ -0,0 +1,68 @@ +fileFormatVersion: 2 +guid: b5679d7447d4b88429639d07ffae9caa +timeCreated: 1497243946 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Docs/QuickToggle.png b/Docs/QuickToggle.png new file mode 100644 index 0000000..9aa2324 Binary files /dev/null and b/Docs/QuickToggle.png differ diff --git a/Docs/QuickToggle.png.meta b/Docs/QuickToggle.png.meta new file mode 100644 index 0000000..e328985 --- /dev/null +++ b/Docs/QuickToggle.png.meta @@ -0,0 +1,68 @@ +fileFormatVersion: 2 +guid: bfb6261706e9bc748a85c0a751601c06 +timeCreated: 1497243946 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..99ac69f --- /dev/null +++ b/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b48a1bdc6397c4379b3e28a4e25ab0b0 +folderAsset: yes +timeCreated: 1447732428 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/QuickToggle.cs b/Editor/QuickToggle.cs new file mode 100644 index 0000000..0cef9da --- /dev/null +++ b/Editor/QuickToggle.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace UnityToolbag +{ + [InitializeOnLoad] + public class QuickToggle + { + #region Constants + private const string PrefKeyShowToggle = "UnityToolbag.QuickToggle.Visible"; + private const string PrefKeyShowDividers = "UnityToolbag.QuickToggle.Dividers"; + private const string PrefKeyShowIcons = "UnityToolbag.QuickToggle.Icons"; + + private const string MENU_NAME = "Window/Hierarchy Quick Toggle/Show Toggles"; + private const string MENU_DIVIDER = "Window/Hierarchy Quick Toggle/Dividers"; + private const string MENU_ICONS = "Window/Hierarchy Quick Toggle/Object Icons"; + #endregion + + private static readonly Type HierarchyWindowType; + private static readonly MethodInfo getObjectIcon; + + private static bool stylesBuilt; + private static GUIStyle styleLock, styleUnlocked, + styleVisOn, styleVisOff, + styleDivider; + + private static bool showDivider, showIcons; + + #region Menu stuff + [MenuItem(MENU_NAME, false, 1)] + private static void QuickToggleMenu() + { + bool toggle = EditorPrefs.GetBool(PrefKeyShowToggle); + ShowQuickToggle(!toggle); + Menu.SetChecked(MENU_NAME, !toggle); + } + + [MenuItem(MENU_NAME, true)] + private static bool SetupMenuCheckMarks() + { + Menu.SetChecked(MENU_NAME, EditorPrefs.GetBool(PrefKeyShowToggle)); + Menu.SetChecked(MENU_DIVIDER, EditorPrefs.GetBool(PrefKeyShowDividers)); + Menu.SetChecked(MENU_ICONS, EditorPrefs.GetBool(PrefKeyShowIcons)); + return true; + } + + [MenuItem(MENU_DIVIDER, false, 20)] + private static void ToggleDivider() + { + ToggleSettings(PrefKeyShowDividers, MENU_DIVIDER, out showDivider); + } + + [MenuItem(MENU_ICONS, false, 21)] + private static void ToggleIcons() + { + ToggleSettings(PrefKeyShowIcons, MENU_ICONS, out showIcons); + } + + private static void ToggleSettings(string prefKey, string menuString, out bool valueBool) + { + valueBool = !EditorPrefs.GetBool(prefKey); + EditorPrefs.SetBool(prefKey, valueBool); + Menu.SetChecked(menuString, valueBool); + EditorApplication.RepaintHierarchyWindow(); + } + #endregion + + static QuickToggle() + { + // Setup initial state of editor prefs + string[] resetPrefs = new string[] {PrefKeyShowToggle, PrefKeyShowDividers, PrefKeyShowIcons}; + foreach (string prefKey in resetPrefs) + { + if (EditorPrefs.HasKey(prefKey) == false) + EditorPrefs.SetBool(prefKey, false); + } + + // Fetch some reflection/type stuff for use later on + Assembly editorAssembly = typeof(EditorWindow).Assembly; + HierarchyWindowType = editorAssembly.GetType("UnityEditor.SceneHierarchyWindow"); + + var flags = BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic; + Type editorGuiUtil = typeof (EditorGUIUtility); + getObjectIcon = editorGuiUtil.GetMethod("GetIconForObject", flags, null, new Type[] { typeof(UnityEngine.Object) }, null); + + // Not calling BuildStyles() in constructor because script gets loaded + // on Unity initialization, styles might not be loaded yet + + // Reset mouse state and + ResetVars(); + ShowQuickToggle(EditorPrefs.GetBool(PrefKeyShowToggle)); + } + + private static void ShowQuickToggle(bool show) + { + EditorPrefs.SetBool(PrefKeyShowToggle, show); + showDivider = EditorPrefs.GetBool(PrefKeyShowDividers, false); + showIcons = EditorPrefs.GetBool(PrefKeyShowIcons, false); + + if (show) + { + ResetVars(); + EditorApplication.update += HandleEditorUpdate; + EditorApplication.hierarchyWindowItemOnGUI += DrawHierarchyItem; + } + else + { + EditorApplication.update -= HandleEditorUpdate; + EditorApplication.hierarchyWindowItemOnGUI -= DrawHierarchyItem; + } + EditorApplication.RepaintHierarchyWindow(); + } + + private struct PropagateState + { + public bool isVisibility; + public bool propagateValue; + + public PropagateState(bool isVisibility, bool propagateValue) + { + this.isVisibility = isVisibility; + this.propagateValue = propagateValue; + } + } + + private static PropagateState propagateState; + + // Because we can't hook into OnGUI of HierarchyWindow, doing a hack + // button that involves the editor update loop and the hierarchy item draw event + private static bool isFrameFresh; + private static bool isMousePressed; + + private static void ResetVars() + { + isFrameFresh = false; + isMousePressed = false; + } + + private static void HandleEditorUpdate() + { + EditorWindow window = EditorWindow.mouseOverWindow; + if (window == null) + { + ResetVars(); + return; + } + + if (window.GetType() == HierarchyWindowType) + { + if (window.wantsMouseMove == false) + window.wantsMouseMove = true; + + isFrameFresh = true; + } + } + + private static void DrawHierarchyItem(int instanceId, Rect selectionRect) + { + BuildStyles(); + + GameObject target = EditorUtility.InstanceIDToObject(instanceId) as GameObject; + if (target == null) + return; + + // Reserve the draw rects + Rect visRect = new Rect(selectionRect) + { + xMin = selectionRect.xMax - (selectionRect.height * 2.1f), + xMax = selectionRect.xMax - selectionRect.height + }; + Rect lockRect = new Rect(selectionRect) + { + xMin = selectionRect.xMax - selectionRect.height + }; + + // Get states + bool isVisible = target.activeSelf; + bool isLocked = (target.hideFlags & HideFlags.NotEditable) > 0; + + // Draw the visibility toggle + GUIStyle visStyle = (isVisible) ? styleVisOn : styleVisOff; + GUI.Label(visRect, GUIContent.none, visStyle); + + // Draw lock toggle + GUIStyle lockStyle = (isLocked) ? styleLock : styleUnlocked; + GUI.Label(lockRect, GUIContent.none, lockStyle); + + // Draw optional divider + if (showDivider) + { + Rect lineRect = new Rect(selectionRect) + { + yMin = selectionRect.yMax - 1f, + yMax = selectionRect.yMax + 2f + }; + GUI.Label(lineRect, GUIContent.none, styleDivider); + } + // Draw optional object icons + if (showIcons && getObjectIcon != null) + { + Texture2D iconImg = getObjectIcon.Invoke(null, new object[] { target }) as Texture2D; + if (iconImg != null) + { + Rect iconRect = new Rect(selectionRect) + { + xMin = visRect.xMin - 30, + xMax = visRect.xMin - 5 + }; + GUI.DrawTexture(iconRect, iconImg, ScaleMode.ScaleToFit); + } + } + + if (Event.current == null) + return; + + HandleMouse(target, isVisible, isLocked, visRect, lockRect); + } + + private static void HandleMouse(GameObject target, bool isVisible, bool isLocked, Rect visRect, Rect lockRect) + { + Event evt = Event.current; + + bool toggleActive = visRect.Contains(evt.mousePosition); + bool toggleLock = lockRect.Contains(evt.mousePosition); + bool stateChanged = (toggleActive || toggleLock); + + bool doMouse = false; + switch (evt.type) + { + case EventType.MouseDown: + // Checking is frame fresh so mouse state is only tested once per frame + // instead of every time a hierarchy item is drawn + bool isMouseDown = false; + if (isFrameFresh && stateChanged) + { + isMouseDown = !isMousePressed; + isMousePressed = true; + isFrameFresh = false; + } + + if (stateChanged && isMouseDown) + { + doMouse = true; + if (toggleActive) isVisible = !isVisible; + if (toggleLock) isLocked = !isLocked; + + propagateState = new PropagateState(toggleActive, (toggleActive) ? isVisible : isLocked); + evt.Use(); + } + break; + case EventType.MouseDrag: + doMouse = isMousePressed; + break; + case EventType.DragPerform: + case EventType.DragExited: + case EventType.DragUpdated: + case EventType.MouseUp: + ResetVars(); + break; + } + + if (doMouse && stateChanged) + { + if (propagateState.isVisibility) + SetVisible(target, propagateState.propagateValue); + else + SetLockObject(target, propagateState.propagateValue); + + EditorApplication.RepaintHierarchyWindow(); + } + } + + private static Object[] GatherObjects(GameObject root) + { + List objects = new List(); + Stack recurseStack = new Stack(new GameObject[] { root }); + + while (recurseStack.Count > 0) + { + GameObject obj = recurseStack.Pop(); + objects.Add(obj); + + foreach (Transform childT in obj.transform) + recurseStack.Push(childT.gameObject); + } + return objects.ToArray(); + } + + private static void SetLockObject(GameObject target, bool isLocked) + { + bool objectLockState = (target.hideFlags & HideFlags.NotEditable) > 0; + if (objectLockState == isLocked) + return; + + Object[] objects = GatherObjects(target); + + foreach (Object obj in objects) + { + GameObject go = (GameObject)obj; + string undoString = string.Format("{0} {1}", isLocked ? "Lock" : "Unlock", go.name); + Undo.RecordObject(go, undoString); + + // Set state according to isLocked + if (isLocked) + { + go.hideFlags |= HideFlags.NotEditable; + } + else + { + go.hideFlags &= ~HideFlags.NotEditable; + } + + // Set hideflags of components + foreach (Component comp in go.GetComponents()) + { + if (comp is Transform) + continue; + Undo.RecordObject(comp, undoString); + if (isLocked) + { + comp.hideFlags |= HideFlags.NotEditable; + comp.hideFlags |= HideFlags.HideInHierarchy; + } + else + { + comp.hideFlags &= ~HideFlags.NotEditable; + comp.hideFlags &= ~HideFlags.HideInHierarchy; + } + EditorUtility.SetDirty(comp); + } + EditorUtility.SetDirty(go); + } + Undo.IncrementCurrentGroup(); + } + + private static void SetVisible(GameObject target, bool isActive) + { + if (target.activeSelf == isActive) return; + + string undoString = string.Format("{0} {1}", + isActive ? "Show" : "Hide", + target.name); + Undo.RecordObject(target, undoString); + + target.SetActive(isActive); + EditorUtility.SetDirty(target); + } + + private static void BuildStyles() + { + // All of the styles have been built, don't do anything + if (stylesBuilt) + return; + + // Now build the GUI styles + // Using icons different from regular lock button so that + // it would look darker + var tempStyle = GUI.skin.FindStyle("IN LockButton"); + styleLock = new GUIStyle(tempStyle) + { + normal = tempStyle.onNormal, + active = tempStyle.onActive, + hover = tempStyle.onHover, + focused = tempStyle.onFocused, + }; + + // Unselected just makes the normal states have no lock images + tempStyle = GUI.skin.FindStyle("OL Toggle"); + styleUnlocked = new GUIStyle(tempStyle); + + tempStyle = GUI.skin.FindStyle("VisibilityToggle"); + + styleVisOff = new GUIStyle(tempStyle); + styleVisOn = new GUIStyle(tempStyle) + { + normal = new GUIStyleState() { background = tempStyle.onNormal.background } + }; + + styleDivider = GUI.skin.FindStyle("EyeDropperHorizontalLine"); + + stylesBuilt = (styleLock != null && styleUnlocked != null && + styleVisOn != null && styleVisOff != null && + styleDivider != null); + } + } +} \ No newline at end of file diff --git a/Editor/QuickToggle.cs.meta b/Editor/QuickToggle.cs.meta new file mode 100644 index 0000000..9a3ba75 --- /dev/null +++ b/Editor/QuickToggle.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 122638ede509d4054b67b6d7676df6b9 +timeCreated: 1447732429 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE.meta b/LICENSE.meta new file mode 100644 index 0000000..a4aa208 --- /dev/null +++ b/LICENSE.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4a1d6aaf72778a2469126737c26d6cd0 +timeCreated: 1497243829 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..7679325 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +Hierarchy Quick Toggle +=== + +Hierarchy Quick Toggle adds icons in the Hierarchy view to quickly hide or lock objects in the scene, similar to Photoshop's layer view. + +![Quick Toggle](./Docs/QuickToggle.png) + +Usage +--- +Place the QuickToggle folder in your project. The toggles can be turned on and off by going to `Window>Hierarchy Quick Toggle` + +![Hide/Show Quick Toggle](./Docs/QuickToggle-Show.png) diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..d451f57 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 301e5b8bad4e1d342b15461b75731c48 +timeCreated: 1497243946 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: