Skip to content

Commit 34be000

Browse files
garrafotemzage31Lucas Pereira Vasconcelos
authored
Icon Colors (#11)
Combined commits: * Changes folder icons in hierarchy * Update HierarchyFolderIcon.cs * Merge commit 'abd4a400c93429b198a91092a71e581162352c60' as 'Packages/UnityHierarchyFolders' * Using reflection to set folder icons on the scene hierarchy * Added folder icon colors * Added color hardcoded palette * Added shortcut for folder creation * _hasProcessedFrame initial value set to true to ensure frame always processed before icon injection * Fixed icon names for Unity 2020.1 and up Co-authored-by: Mohammad Zamanian <mzage31@yahoo.com> Co-authored-by: Lucas Pereira Vasconcelos <lucas.pv@gilp.studio>
1 parent 76b68e7 commit 34be000

File tree

5 files changed

+238
-30
lines changed

5 files changed

+238
-30
lines changed

Editor/FolderEditor.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Threading.Tasks;
2+
using UnityEditor;
3+
using UnityEditor.Experimental;
4+
using UnityEngine;
5+
using UnityHierarchyFolders.Runtime;
6+
7+
namespace UnityHierarchyFolders.Editor
8+
{
9+
[CustomEditor(typeof(Folder))]
10+
public class FolderEditor : UnityEditor.Editor
11+
{
12+
public override bool RequiresConstantRepaint()
13+
{
14+
return true;
15+
}
16+
17+
public override void OnInspectorGUI()
18+
{
19+
DoIconColorPicker();
20+
}
21+
22+
private void DoIconColorPicker()
23+
{
24+
GUILayout.Label("Icon Color", EditorStyles.boldLabel);
25+
26+
SerializedProperty colorIndexProperty = serializedObject.FindProperty("_colorIndex");
27+
28+
EditorGUILayout.BeginHorizontal();
29+
GUILayout.FlexibleSpace();
30+
31+
var buttonSize = 25f;
32+
33+
var gridRect = EditorGUILayout.GetControlRect(false, buttonSize * HierarchyFolderIcon.IconRowCount,
34+
GUILayout.Width(buttonSize * HierarchyFolderIcon.IconColumnCount));
35+
36+
int currentIndex = colorIndexProperty.intValue;
37+
for (int row = 0; row < HierarchyFolderIcon.IconRowCount; row++)
38+
{
39+
for (int column = 0; column < HierarchyFolderIcon.IconColumnCount; column++)
40+
{
41+
int index = 1 + column + row * HierarchyFolderIcon.IconColumnCount;
42+
float width = gridRect.width / HierarchyFolderIcon.IconColumnCount;
43+
float height = gridRect.height / HierarchyFolderIcon.IconRowCount;
44+
Rect rect = new Rect(gridRect.x + width * column, gridRect.y + height * row, width, height);
45+
var (icon, _) = HierarchyFolderIcon.coloredFolderIcons[index];
46+
47+
if (Event.current.type == EventType.Repaint)
48+
{
49+
if (index == currentIndex)
50+
{
51+
GUIStyle hover = "TV Selection";
52+
hover.Draw(rect, false, false, false, false);
53+
}
54+
else if (rect.Contains(Event.current.mousePosition))
55+
{
56+
GUI.backgroundColor = new Color(.7f, .7f, .7f, 1f);
57+
GUIStyle white = "WhiteBackground";
58+
white.Draw(rect, false, false, true, false);
59+
GUI.backgroundColor = Color.white;
60+
}
61+
}
62+
63+
if (GUI.Button(rect, icon, EditorStyles.label))
64+
{
65+
Undo.RecordObject(target, "Set Folder Color");
66+
colorIndexProperty.intValue = currentIndex == index ? 0 : index;
67+
serializedObject.ApplyModifiedProperties();
68+
EditorApplication.RepaintHierarchyWindow();
69+
GUIUtility.ExitGUI();
70+
}
71+
}
72+
}
73+
74+
GUILayout.FlexibleSpace();
75+
EditorGUILayout.EndHorizontal();
76+
77+
GUILayout.Space(10f);
78+
}
79+
}
80+
}

Editor/FolderEditor.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/FolderEditorUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace UnityHierarchyFolders.Editor
99
{
1010
public static class FolderEditorUtils
1111
{
12-
const string actionName = "Create Heirarchy Folder";
12+
const string actionName = "Create Heirarchy Folder %#&N";
1313

1414
/// <summary>Add new folder "prefab".</summary>
1515
/// <param name="command">Menu command information.</param>

Editor/HierarchyFolderIcon.cs

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,24 @@
1010
using UnityHierarchyFolders.Runtime;
1111
using Object = UnityEngine.Object;
1212

13-
namespace Plugins.UnityHierarchyFolders {
13+
namespace UnityHierarchyFolders.Editor
14+
{
1415
public class HierarchyFolderIcon
1516
{
17+
#if UNITY_2020_1_OR_NEWER
18+
private const string OpenedFolderPrefix = "FolderOpened";
19+
#else
20+
private const string OpenedFolderPrefix = "OpenedFolder";
21+
#endif
22+
private const string ClosedFolderPrefix = "Folder";
23+
1624
private static Texture2D openFolderTexture;
1725
private static Texture2D closedFolderTexture;
1826
private static Texture2D openFolderSelectedTexture;
1927
private static Texture2D closedFolderSelectedTexture;
2028

2129
private static bool isInitialized;
22-
private static bool hasProcessedFrame;
30+
private static bool hasProcessedFrame = true;
2331

2432
// Reflected members
2533
private static PropertyInfo prop_sceneHierarchy;
@@ -32,29 +40,68 @@ public class HierarchyFolderIcon
3240
private static MethodInfo meth_isExpanded;
3341
private static MethodInfo meth_getAllSceneHierarchyWindows;
3442

43+
private static (Texture2D, Texture2D)[] _coloredFolderIcons;
44+
public static (Texture2D, Texture2D)[] coloredFolderIcons => _coloredFolderIcons;
45+
46+
public static int IconColumnCount => IconColors.GetLength(0);
47+
public static int IconRowCount => IconColors.GetLength(1);
48+
49+
public static readonly Color[,] IconColors = {
50+
{new Color(0.09f, 0.57f, 0.82f), new Color(0.05f, 0.34f, 0.48f),},
51+
{new Color(0.09f, 0.67f, 0.67f), new Color(0.05f, 0.42f, 0.42f),},
52+
{new Color(0.23f, 0.73f, 0.36f), new Color(0.15f, 0.41f, 0.22f),},
53+
{new Color(0.55f, 0.35f, 0.71f), new Color(0.35f, 0.24f, 0.44f),},
54+
{new Color(0.78f, 0.27f, 0.55f), new Color(0.52f, 0.15f, 0.35f),},
55+
{new Color(0.80f, 0.66f, 0.10f), new Color(0.56f, 0.46f, 0.02f),},
56+
{new Color(0.91f, 0.49f, 0.13f), new Color(0.62f, 0.33f, 0.07f),},
57+
{new Color(0.91f, 0.30f, 0.24f), new Color(0.77f, 0.15f, 0.09f),},
58+
{new Color(0.35f, 0.49f, 0.63f), new Color(0.24f, 0.33f, 0.42f),},
59+
};
60+
3561
[InitializeOnLoadMethod]
3662
static void Startup()
3763
{
38-
Initialize();
3964
EditorApplication.update += ResetFolderIcons;
4065
EditorApplication.hierarchyWindowItemOnGUI += RefreshFolderIcons;
4166
}
4267

43-
private static Texture2D GetTintedTexture(Texture2D original)
68+
private static Texture2D GetTintedTexture(Texture2D original, Color tint, string name = "")
69+
{
70+
Color32 TintColor(Color32 c)
71+
{
72+
return c * tint;
73+
}
74+
75+
return GetColorizedTexture(original, TintColor, name);
76+
}
77+
78+
private static Texture2D GetWhiteTexture(Texture2D original, string name = "")
79+
{
80+
Color32 MakeColorsWhite(Color32 c)
81+
{
82+
byte a = c.a;
83+
c = Color.HSVToRGB(0, 0, 1);
84+
c.a = a;
85+
86+
return c;
87+
}
88+
89+
return GetColorizedTexture(original, MakeColorsWhite, name);
90+
}
91+
92+
private static Texture2D GetColorizedTexture(Texture2D original, Func<Color32, Color32> colorManipulator, string name = "")
4493
{
4594
var tinted = new Texture2D(original.width, original.height,
4695
original.graphicsFormat, original.mipmapCount, TextureCreationFlags.MipChain);
96+
97+
tinted.name = name;
98+
99+
Graphics.CopyTexture(original, tinted);
47100

48-
Graphics.CopyTexture(original, tinted);
49101
var data = tinted.GetRawTextureData<Color32>();
50102
for (int index = 0, len = data.Length; index < len; index++)
51103
{
52-
Color32 c = data[index];
53-
byte a = c.a;
54-
c = Color.HSVToRGB(0, 0, 1);
55-
c.a = a;
56-
57-
data[index] = c;
104+
data[index] = colorManipulator(data[index]);
58105
}
59106

60107
var mipmapSize = tinted.width * tinted.height * 4;
@@ -65,20 +112,44 @@ private static Texture2D GetTintedTexture(Texture2D original)
65112
mipmapSize >>= 2;
66113
offset += mipmapSize;
67114
}
115+
116+
tinted.hideFlags = HideFlags.DontSave;
68117
tinted.Apply();
69118

70119
return tinted;
71120
}
72-
73-
private static void Initialize()
121+
122+
private static void InitIfNeeded()
74123
{
75124
if (isInitialized) { return; }
76-
77-
closedFolderTexture = (Texture2D) EditorGUIUtility.IconContent("Folder Icon").image;
78-
openFolderTexture = (Texture2D) EditorGUIUtility.IconContent("FolderEmpty Icon").image;
79-
80-
openFolderSelectedTexture = GetTintedTexture(openFolderTexture);
81-
closedFolderSelectedTexture = GetTintedTexture(closedFolderTexture);
125+
126+
openFolderTexture = (Texture2D) EditorGUIUtility.IconContent($"{OpenedFolderPrefix} Icon").image;
127+
closedFolderTexture = (Texture2D) EditorGUIUtility.IconContent($"{ClosedFolderPrefix} Icon").image;
128+
129+
// We could use the actual white folder icons but I prefer the look of the tinted folder icon
130+
// so I'm leaving this as a documented alternative.
131+
//openFolderSelectedTexture = (Texture2D) EditorGUIUtility.IconContent($"{OpenedFolderPrefix} On Icon").image;
132+
//closedFolderSelectedTexture = (Texture2D) EditorGUIUtility.IconContent($"{ClosedFolderPrefix} On Icon").image;
133+
openFolderSelectedTexture = GetWhiteTexture(openFolderTexture, $"{OpenedFolderPrefix} Icon White");
134+
closedFolderSelectedTexture = GetWhiteTexture(closedFolderTexture, $"{ClosedFolderPrefix} Icon White");
135+
136+
_coloredFolderIcons = new (Texture2D, Texture2D)[] {(openFolderTexture, closedFolderTexture)};
137+
138+
for (int row = 0; row < IconRowCount; row++)
139+
{
140+
for (int column = 0; column < IconColumnCount; column++)
141+
{
142+
int index = 1 + column + row * IconColumnCount;
143+
var color = IconColors[column, row];
144+
145+
var openFolderIcon = GetTintedTexture(openFolderSelectedTexture,
146+
color, $"{openFolderSelectedTexture.name} {index}");
147+
var closedFolderIcon = GetTintedTexture(closedFolderSelectedTexture,
148+
color, $"{closedFolderSelectedTexture.name} {index}");
149+
150+
ArrayUtility.Add(ref _coloredFolderIcons, (openFolderIcon, closedFolderIcon));
151+
}
152+
}
82153

83154
const BindingFlags BindingAll = BindingFlags.Public
84155
| BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
@@ -108,31 +179,37 @@ private static void Initialize()
108179

109180
private static void ResetFolderIcons()
110181
{
111-
Initialize();
182+
InitIfNeeded();
112183
hasProcessedFrame = false;
113184
}
114185

115186
private static void RefreshFolderIcons(int instanceid, Rect selectionrect)
116187
{
117188
if (hasProcessedFrame) { return; }
189+
118190
hasProcessedFrame = true;
119191

120192
var windows = ((IEnumerable)meth_getAllSceneHierarchyWindows.Invoke(null, Array.Empty<object>())).Cast<EditorWindow>().ToList();
121-
foreach (EditorWindow h in windows)
193+
foreach (EditorWindow window in windows)
122194
{
123-
var sceneHierarchy = prop_sceneHierarchy.GetValue(h);
195+
var sceneHierarchy = prop_sceneHierarchy.GetValue(window);
124196
var treeView = prop_treeView.GetValue(sceneHierarchy);
125197
var data = prop_data.GetValue(treeView);
126198

127199
var rows = (IList<TreeViewItem>) meth_getRows.Invoke(data, Array.Empty<object>());
128200
foreach (TreeViewItem item in rows)
129201
{
130202
var itemObject = (Object) prop_objectPPTR.GetValue(item);
131-
if (!itemObject || !Folder.IsFolder(itemObject)) { continue; }
203+
if (!Folder.TryGetIconIndex(itemObject, out int colorIndex)) { continue; }
132204

205+
133206
var isExpanded = (bool) meth_isExpanded.Invoke(data, new object[] { item });
134207

135-
item.icon = isExpanded ? openFolderTexture : closedFolderTexture;
208+
Texture2D open, closed;
209+
var count = _coloredFolderIcons.Length;
210+
(open, closed) = coloredFolderIcons[Mathf.Clamp(colorIndex, 0, count) % count];
211+
item.icon = isExpanded ? open : closed;
212+
136213
prop_selectedIcon.SetValue(item, isExpanded ? openFolderSelectedTexture : closedFolderSelectedTexture);
137214
}
138215
}

Runtime/Folder.cs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,61 @@ private Folder()
5555

5656
private static Tool lastTool;
5757
private static Folder toolLock;
58+
59+
[SerializeField]
60+
private int _colorIndex = 0;
61+
62+
public int colorIndex => _colorIndex;
5863

5964
/// <summary>
6065
/// The set of folder objects.
6166
/// </summary>
62-
private static HashSet<int> folders = new HashSet<int>();
67+
public static Dictionary<int, int> folders = new Dictionary<int, int>();
68+
69+
/// <summary>
70+
/// Gets the icon index associated with the specified object.
71+
/// </summary>
72+
/// <param name="obj">Test object.</param>
73+
/// <param name="index">The icon index.</param>
74+
/// <returns>True if the specified object is a folder with a registered icon index.</returns>
75+
public static bool TryGetIconIndex(UnityEngine.Object obj, out int index)
76+
{
77+
index = -1;
78+
return obj && folders.TryGetValue(obj.GetInstanceID(), out index);
79+
}
6380

6481
/// <summary>
6582
/// Test if a Unity object is a folder by way of containing a Folder component.
6683
/// </summary>
6784
/// <param name="obj">Test object.</param>
6885
/// <returns>Is this object a folder?</returns>
69-
public static bool IsFolder(UnityEngine.Object obj) => folders.Contains(obj.GetInstanceID());
86+
public static bool IsFolder(UnityEngine.Object obj) => folders.ContainsKey(obj.GetInstanceID());
87+
88+
private void Start() => AddOrUpdateFolderData();
89+
private void OnValidate() => AddOrUpdateFolderData();
90+
private void OnDestroy() => RemoveFolderData();
91+
92+
private void RemoveFolderData()
93+
{
94+
var instanceId = gameObject.GetInstanceID();
95+
if (folders.ContainsKey(instanceId))
96+
{
97+
folders.Remove(gameObject.GetInstanceID());
98+
}
99+
}
70100

71-
private void Start() => folders.Add(gameObject.GetInstanceID());
72-
private void OnDestroy() => folders.Remove(gameObject.GetInstanceID());
101+
private void AddOrUpdateFolderData()
102+
{
103+
var instanceId = gameObject.GetInstanceID();
104+
if (folders.ContainsKey(instanceId))
105+
{
106+
folders[instanceId] = _colorIndex;
107+
}
108+
else
109+
{
110+
folders.Add(instanceId, _colorIndex);
111+
}
112+
}
73113

74114
/// <summary>Hides all gizmos if selected to avoid accidental editing of the transform.</summary>
75115
private void HandleSelection()
@@ -157,7 +197,7 @@ private void Update()
157197
#if UNITY_EDITOR
158198
if (!Application.IsPlaying(gameObject))
159199
{
160-
folders.Add(gameObject.GetInstanceID());
200+
AddOrUpdateFolderData();
161201
}
162202

163203
this.EnsureExclusiveComponent();

0 commit comments

Comments
 (0)