-
-
Save TanaTanoi/ca37a35628b5f8dcb47e67def3d7ee6b to your computer and use it in GitHub Desktop.
The DPC Editor tools framework, for making tool dev easy in Unity!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEditor; | |
using UnityEngine; | |
namespace DPCEditorTools | |
{ | |
/// <summary> | |
/// An IEditorTool that manages the visibility of other editor tools. | |
/// </summary> | |
public class ActiveEditorToolsToggler : IEditorTool | |
{ | |
string IEditorTool.Header => "Enabled Tools"; | |
public void DrawTool(EditorToolsWindow callingEditorTool) | |
{ | |
foreach (ToolState toolState in callingEditorTool.ToolStates) | |
{ | |
// Skip this tool, can't disable it else we'd be in trouble. | |
if (toolState.tool == this) | |
{ | |
continue; | |
} | |
DrawEditorCheckForTool(toolState, toolState.GetFormattedHeaderName()); | |
} | |
if (GUILayout.Button("Show All")) | |
{ | |
foreach (ToolState tool in callingEditorTool.ToolStates) | |
{ | |
tool.IsEnabled = true; | |
} | |
} | |
if (GUILayout.Button("Hide All")) | |
{ | |
foreach (ToolState tool in callingEditorTool.ToolStates) | |
{ | |
tool.IsEnabled = false; | |
} | |
} | |
} | |
private void DrawEditorCheckForTool(ToolState toolState, string label) | |
{ | |
GUIStyle style = new GUIStyle(EditorStyles.label); | |
if (toolState.IsFavourited) | |
{ | |
style.normal.textColor = Color.yellow; | |
style.onNormal.textColor = Color.yellow; | |
} | |
toolState.IsEnabled = EditorGUILayout.ToggleLeft(label, toolState.IsEnabled, labelStyle: style); | |
} | |
public void OnCreated() | |
{ | |
} | |
public bool OnlyShowWhenAGameIsAvailable() | |
{ | |
return false; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEditor; | |
namespace DPCEditorTools | |
{ | |
public class DPCIndividualEditorToolWindow : EditorWindow | |
{ | |
private IEditorTool _windowedTool; | |
private IEditorTool WindowedTool | |
{ | |
get | |
{ | |
if (_windowedTool == null) | |
{ | |
// In the event this window is created by other means (e.g. re-opening unity) | |
// Then we need to pull the window from the main ToolsWindow by name. | |
_windowedTool = ToolsWindow.GetToolByTypeString(titleContent.text).tool; | |
} | |
return _windowedTool; | |
} | |
} | |
private static EditorToolsWindow _toolsWindow; | |
private static EditorToolsWindow ToolsWindow | |
{ | |
get | |
{ | |
if (_toolsWindow == null) | |
{ | |
_toolsWindow = GetWindow<EditorToolsWindow>(); | |
} | |
return _toolsWindow; | |
} | |
} | |
public static void CreateWindow(Type type) | |
{ | |
CreateWindow<DPCIndividualEditorToolWindow>(type.ToString()); | |
} | |
private void OnGUI() | |
{ | |
if (ToolsWindow != null && WindowedTool != null) | |
{ | |
WindowedTool.DrawTool(ToolsWindow); | |
} | |
else | |
{ | |
EditorGUILayout.LabelField("Please open the DPC Editor Window somewhere else too."); | |
} | |
} | |
private void OnDestroy() | |
{ | |
if (ToolsWindow != null && WindowedTool != null) | |
{ | |
// On Destroy, enable it back on the Editor Tools Window. | |
ToolsWindow.GetToolByTypeString(WindowedTool.GetType().ToString()).IsEnabled = true; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using UnityEngine.Profiling; | |
namespace DPCEditorTools | |
{ | |
/// <summary> | |
/// EditorToolsWindow hosts all IEditorTools and is where you add new IEditorTool implementations. | |
/// It also manages which tools are expanded, saving state to EditorPrefs to ensure consistentcy. | |
/// </summary> | |
public class EditorToolsWindow : EditorWindow | |
{ | |
[MenuItem("Window/DPC Editor Tools")] | |
public static void OpenDPCEditorToolsWindow() | |
{ | |
GetWindow<EditorToolsWindow>(false, "DPC Editor Tools", true); | |
} | |
// Add your tools here! The order is the order in which they appear in the window. | |
private readonly List<Type> _toolTypes = new List<Type> | |
{ | |
typeof(ShortcutsEditorTool), | |
}; | |
private readonly Dictionary<Type, ToolState> _toolStates = new Dictionary<Type, ToolState>(); | |
public IEnumerable<ToolState> ToolStates => _toolStates.Values; | |
private Vector2 _globalScrollView = Vector2.zero; | |
private static GUIStyle _defaultHeaderStyle; | |
private static GUIStyle _favouriteHeaderStyle; | |
// We use an Initialise method here instead of a unity invoked Awake because the call to `new GUIStyle` | |
// was throwing a NullReferenceException when the method was Awake. | |
// I'm not exactly sure what was causing it, but here is a link to someone else experiencing the issue: | |
// https://github.com/SubjectNerd-Unity/ReorderableInspector/issues/14 | |
private void Initialise() | |
{ | |
_defaultHeaderStyle = new GUIStyle(EditorStyles.foldoutHeader) | |
{ | |
fontSize = 14, | |
fixedHeight = EditorStyles.foldoutHeader.fixedHeight + 5 | |
}; | |
_favouriteHeaderStyle = new GUIStyle(_defaultHeaderStyle) | |
{ | |
onNormal = { textColor = Color.yellow }, | |
normal = { textColor = Color.yellow }, | |
onHover = { textColor = Color.yellow }, | |
hover = { textColor = Color.yellow } | |
}; | |
InitializeTool(typeof(ActiveEditorToolsToggler)); | |
foreach (Type toolType in _toolTypes) | |
{ | |
InitializeTool(toolType); | |
} | |
} | |
private void OnGUI() | |
{ | |
Profiler.BeginSample("DPCEditorTool.OnGUI"); | |
if (_toolStates.Count == 0) | |
{ | |
Initialise(); | |
} | |
_globalScrollView = EditorGUILayout.BeginScrollView(_globalScrollView); | |
EditorGUILayout.BeginVertical(); | |
DrawTool(typeof(ActiveEditorToolsToggler), EditorStyles.foldoutHeader); | |
foreach (Type toolType in _toolTypes) | |
{ | |
DrawTool(toolType); | |
} | |
EditorGUILayout.EndVertical(); | |
EditorGUILayout.EndScrollView(); | |
Profiler.EndSample(); | |
} | |
private void InitializeTool(Type toolType) | |
{ | |
if (!_toolStates.ContainsKey(toolType)) | |
{ | |
IEditorTool newInstance = (IEditorTool)Activator.CreateInstance(toolType); | |
newInstance.OnCreated(); | |
_toolStates.Add(toolType, new ToolState(newInstance)); | |
} | |
} | |
private Action<Rect> GetContextMenuActionForTool(Type toolType) | |
{ | |
// Don't need one for the ActiveEditorToolsToggler. | |
if (toolType == typeof(ActiveEditorToolsToggler)) | |
{ | |
return null; | |
} | |
return (Rect rect) => | |
{ | |
GenericMenu menu = new GenericMenu(); | |
menu.AddItem(new GUIContent("Open as new window"), false, () => | |
{ | |
CreateIndividualWindowForTool(toolType); | |
// Hide it so it can appear in a window instead | |
_toolStates[toolType].IsEnabled = false; | |
}); | |
menu.AddItem(new GUIContent("Create new window of tool"), false, () => CreateIndividualWindowForTool(toolType)); | |
menu.AddItem(new GUIContent("Hide tool"), false, () => _toolStates[toolType].IsEnabled = false); | |
menu.AddItem(new GUIContent("Toggle favourite"), false, () => _toolStates[toolType].ToggleIsFavourited()); | |
menu.DropDown(rect); | |
}; | |
} | |
public ToolState GetToolByTypeString(string toolTypeString) | |
{ | |
if (_toolStates.Count == 0) | |
{ | |
Initialise(); | |
} | |
foreach (Type toolType in _toolStates.Keys) | |
{ | |
if (toolType.ToString() == toolTypeString) | |
{ | |
return _toolStates[toolType]; | |
} | |
} | |
return null; | |
} | |
private void CreateIndividualWindowForTool(Type toolType) | |
{ | |
DPCIndividualEditorToolWindow.CreateWindow(toolType); | |
} | |
private void DrawTool(Type toolType, GUIStyle styleOverride = null) | |
{ | |
Profiler.BeginSample($"DPCEditorTool.DrawTool({toolType.Name})"); | |
if (!_toolStates.TryGetValue(toolType, out ToolState activeToolState)) | |
{ | |
Debug.LogError($"There is no tool present for {toolType}!"); | |
return; | |
} | |
GUIStyle style = styleOverride ?? (activeToolState.IsFavourited ? _favouriteHeaderStyle : _defaultHeaderStyle); | |
// Ensure the one that can hide everything (ActiveEditorToolsToggler) can't be hidden. | |
if (toolType == typeof(ActiveEditorToolsToggler) || activeToolState.IsEnabled) | |
{ | |
string title = activeToolState.GetFormattedHeaderName(); | |
activeToolState.IsExpanded = EditorGUILayout.BeginFoldoutHeaderGroup(activeToolState.IsExpanded, title, menuAction: GetContextMenuActionForTool(toolType), style: style); | |
if (activeToolState.IsExpanded) | |
{ | |
using (new EditorGUI.IndentLevelScope()) | |
{ | |
activeToolState.tool.DrawTool(this); | |
} | |
} | |
} | |
EditorGUILayout.EndFoldoutHeaderGroup(); | |
Profiler.EndSample(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace DPCEditorTools | |
{ | |
/// <summary> | |
/// An IEditorTool is used inside the EditorToolsWindow to draw a single, minimisable window. | |
/// </summary> | |
public interface IEditorTool | |
{ | |
/// <summary> | |
/// What is this editor tool called? | |
/// </summary> | |
string Header { get; } | |
/// <summary> | |
/// If there's any on-creation logic you need before the tool is displayed, it can be done in this method. | |
/// This is called within the OnGUI call by EditorToolsWindow, but before DrawTool is called. | |
/// </summary> | |
void OnCreated(); | |
/// <summary> | |
/// This is called as part of the immediate-mode editor OnGUI call by EditorToolsWindow. | |
/// This is only run if the tool is enabled. | |
/// </summary> | |
/// <param name="callingEditorTool"></param> | |
void DrawTool(EditorToolsWindow callingEditorTool); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
namespace DPCEditorTools | |
{ | |
public class ShortcutsEditorTool : IEditorTool | |
{ | |
string IEditorTool.Header => "Shortcuts"; | |
public void OnCreated() { } | |
public void DrawTool(EditorToolsWindow callingEditorTool) | |
{ | |
using (new EditorGUI.DisabledScope(!Application.isPlaying)) | |
{ | |
Time.timeScale = EditorGUILayout.Slider("Unity Timescale", Time.timeScale, 0, 2); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEditor; | |
namespace DPCEditorTools | |
{ | |
/// <summary> | |
/// This class stores other state about a tool, such as if the dropdown is expanded, if the header is favourited | |
/// or if the tool is visible in the main dropdown at all. It also hides all of the editor prefs behind properties, | |
/// which are used to keep the windows consistent between code reloads. | |
/// </summary> | |
public class ToolState | |
{ | |
public ToolState(IEditorTool tool) | |
{ | |
this.tool = tool; | |
} | |
private const string DpcEditorPrefsPrefix = "DPCEditorTool_"; | |
private const string EditorPrefsHeaderExpandedPrefix = DpcEditorPrefsPrefix + "HeaderExpanded_"; | |
private const string EditorPrefsToolEnabledPrefix = DpcEditorPrefsPrefix + "ToolEnabled_"; | |
private const string EditorPrefsToolFavouritedPrefix = DpcEditorPrefsPrefix + "ToolFavourited_"; | |
public IEditorTool tool; | |
public string GetFormattedHeaderName() | |
{ | |
string headerName = tool.Header; | |
if (IsFavourited) | |
{ | |
headerName += "*"; | |
} | |
return headerName; | |
} | |
// Is this window expanded inside the EditorToolsWindow? | |
private bool? _isExpanded; | |
public bool IsExpanded | |
{ | |
get | |
{ | |
if (!_isExpanded.HasValue) | |
{ | |
string key = GetEditorPrefsKeyForToolExpandedState(tool.GetType()); | |
_isExpanded = EditorPrefs.GetBool(key, defaultValue: false); | |
} | |
return _isExpanded.Value; | |
} | |
set | |
{ | |
if (!_isExpanded.HasValue || value != _isExpanded.Value) | |
{ | |
_isExpanded = value; | |
string key = GetEditorPrefsKeyForToolExpandedState(tool.GetType()); | |
EditorPrefs.SetBool(key, _isExpanded.Value); | |
} | |
} | |
} | |
// Is this tool window actually enabled to be displayed at all (even as a header)? | |
private bool? _isEnabled; | |
public bool IsEnabled | |
{ | |
get | |
{ | |
if (!_isEnabled.HasValue) | |
{ | |
string key = GetEditorPrefsDisabledIdForWindow(tool.GetType()); | |
_isEnabled = EditorPrefs.GetBool(key, defaultValue: true); | |
} | |
return _isEnabled.Value; | |
} | |
set | |
{ | |
if (!_isEnabled.HasValue || _isEnabled.Value != value) | |
{ | |
_isEnabled = value; | |
string key = GetEditorPrefsDisabledIdForWindow(tool.GetType()); | |
EditorPrefs.SetBool(key, _isEnabled.Value); | |
} | |
} | |
} | |
// Is this tool window favourited by the user? | |
private bool? _isFavourited; | |
public bool IsFavourited | |
{ | |
get | |
{ | |
if (!_isFavourited.HasValue) | |
{ | |
string key = GetEditorPrefsFavouriteIdForWindow(tool.GetType()); | |
_isFavourited = EditorPrefs.GetBool(key, defaultValue: false); | |
} | |
return _isFavourited.Value; | |
} | |
set | |
{ | |
if (!_isFavourited.HasValue || _isFavourited.Value != value) | |
{ | |
_isFavourited = value; | |
string key = GetEditorPrefsFavouriteIdForWindow(tool.GetType()); | |
EditorPrefs.SetBool(key, _isFavourited.Value); | |
} | |
} | |
} | |
public void ToggleIsFavourited() | |
{ | |
IsFavourited = !IsFavourited; | |
} | |
private string GetEditorPrefsKeyForToolExpandedState(Type toolType) | |
{ | |
return EditorPrefsHeaderExpandedPrefix + toolType.ToString(); | |
} | |
public static string GetEditorPrefsFavouriteIdForWindow(Type toolType) | |
{ | |
return EditorPrefsToolFavouritedPrefix + toolType.ToString(); | |
} | |
public static string GetEditorPrefsDisabledIdForWindow(Type toolType) | |
{ | |
return EditorPrefsToolEnabledPrefix + toolType.ToString(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment