Search Unity

  1. We've closed the job boards. If you're looking for work, or looking to hire check out Unity Connect. You can see more information here.
    Dismiss Notice
  2. We're running great holiday deals on subscriptions, swag and Asset Store packages! Take a peek at this blog for more information!
    Dismiss Notice
  3. Check out our Unite Austin 2017 YouTube playlist to catch up on what you missed. More videos coming soon.
    Dismiss Notice
  4. Unity 2017.2 is now released.
    Dismiss Notice
  5. The Unity Gear Store is here to help you look great at your next meetup, user group or conference. With all new Unity apparel, stickers and more!
    Dismiss Notice
  6. Introducing the Unity Essentials Packs! Find out more.
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice
  8. Unity 2017.3 beta is now available for download.
    Dismiss Notice

Modifying component inspector headers (for component folders)

Discussion in 'Extensions & OnGUI' started by DreamingImLatios, Nov 12, 2017.

  1. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    12
    I'm fairly new to editor extensions and such, and am trying to implement component folders. I tend to build systems with composition which means that I can very easily stack up 30-50 components on a GameObject.

    I actually have nearly all of the basics of the system working. But there's one catch, I can't rename folders in a way that is intuitive.

    ComponentFolders Help 1.PNG

    I have several ideas on how to get around this, but I'm not sure what the best approach is.

    Approach 1. Create a custom Inspector Window to replace the existing window. This might allow me to control the headings, but then I would need to figure out how to draw all the basic GameObject header stuff. This would be the answer if I had a base custom inspector window to go off of, but right now I don't know where to start.

    Approach 2. Try drawing an out-of-bounds rectangle to overlap the header and display the new name. I'm not sure if this is allowed, and if it is, I would prefer to do so without hard-coding the offsets. And then comes the problem of whether or not I can read drag and drop in that region.

    Approach 3. Override OnHeaderGUI or some other method that I'm not aware of. This would also be an ideal solution if I found something that worked. But it seems that an Editor's header is only used for assets and isn't used for Components.

    Approach 4. Create a tool that generates a class which inherits from ComponentFolder every time a folder is renamed. Recompiling code would be terrible and I still probably wouldn't get drag and drop working.

    Any ideas?

    Thanks in advance!
     
  2. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    329
    I got curious reading your post and have been fiddling with Editor scripts. It is unfortunate Approach 3 doesn't work because OnHeaderGUI is internal and not exposed to us, as it seems the most elegant.

    I managed to implement something like Approach 2:
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. [CustomEditor (typeof (ComponentFolder))]
    6. internal class ComponentFolderEditor : Editor
    7. {
    8.     Color proColor = (Color) new Color32 (56, 56, 56, 255);
    9.     Color plebColor = (Color) new Color32 (194, 194, 194, 255);
    10.  
    11.     public override void OnInspectorGUI ()
    12.     {
    13.         OnHeaderGUI();
    14.  
    15.         base.OnInspectorGUI ();
    16.     }
    17.  
    18.     protected override void OnHeaderGUI ()
    19.     {
    20.         var rect = EditorGUILayout.GetControlRect(false, 0f);
    21.         rect.height = EditorGUIUtility.singleLineHeight;
    22.         rect.y -= rect.height;
    23.         rect.x = 48;
    24.         rect.xMax -= rect.x * 2f;
    25.  
    26.         EditorGUI.DrawRect (rect, EditorGUIUtility.isProSkin ? proColor : plebColor);
    27.  
    28.         string header = (target as ComponentFolder).folderName; // <--- your variable
    29.         if (string.IsNullOrEmpty (header))
    30.             header = target.ToString();
    31.  
    32.         EditorGUI.LabelField (rect, header, EditorStyles.boldLabel);
    33.     }
    34. }
    35.  
    36. #endif
    Good thing is, it supports click and drag. One downside is that component has to be expanded in order to run the HeaderGUI code. If you can find a way to call it while collapsed, great!

    As for Approach 1, that might be the best option but definitely the most time-consuming. You could analyse the Editor implementation some guys did with ILSpy and try to recreate a similar implementation in your own EditorWindow: https://github.com/MattRix/UnityDecompiled/blob/master/UnityEditor/UnityEditor/Editor.cs

    Hope it helps!
     
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    12
    Hey! That's pretty cool!

    I'm going to have to do some testing to see if events on the overdrawn rect trump priority over the original header's events. If so, I could make it so that once the drop-down is opened, it couldn't be closed. Even if it means the component always remains double the height of being fully collapsed, that would still be sufficient.

    You also gave me an idea for yet another approach to this problem, but I don't think it will work either. If there was a convenient means to detect whenever a component was added, it could be easily snatched and added to a list in a ComponentFolderSystem class which would be a required component by all ComponentFolders and would do all of the drawing for components.

    Anyways, thank you for the help!

    I'm probably going to test this with some event handling over the next weekend and see if custom events can block the minimizing event. If it works, it will only be a matter of time before I get this system into a clean enough state that I can open source it!
     
  4. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    329
    Actually, there is an "elegant" way to handle added component, with Reset() function in Editor subclass.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. #if UNITY_EDITOR
    5. using UnityEditor;
    6.  
    7. [CustomEditor (typeof (ComponentFolder))]
    8. internal class ComponentFolderEditor : Editor
    9. {
    10.      void Reset ()
    11.      {
    12.           var folder = target as ComponentFolder;
    13.           int count = folder.GetComponents<Component>().Length;
    14.           if (folder. lastComponentCount != count)
    15.           {
    16.                folder.lastComponentCount = count;
    17.            
    18.                // your event handling solution
    19.                folder.OnComponentsChanged.Invoke ();
    20.           }
    21.      }
    22. }
    23.  
    24. #endif
    25.  
    26. public class ComponentFolder : MonoBehaviour
    27. {
    28.      [HideInInspector]
    29.      [SerializeField]
    30.      internal int lastComponentCount = 0;
    31.  
    32.      public UnityEvent OnComponentsChanged = new UnityEvent();
    33. }
    However, this is editor only. Hope it helps!
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    12
    So I got to test out all of these today.

    Regarding overriding Unity's events, I'm not even close. Unity's InspectorWindow seems to trump everything.

    I do want to know how you figured out that an Editor subclass has a Reset callback! There is no documentation on it anywhere. But it works as you said, so I guess that is my solution. This will take me a couple weeks before I find more time to rebuild my system with the new scheme.

    Thanks for all your help!
     
  6. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    329
    There is some obscure documentation for MonoBehaviour:
    https://docs.unity3d.com/ScriptReference/MonoBehaviour.Reset.html

    Editor subclasses from ScriptableObject, and ScriptableObject has some similar lifecycle methods like OnEnabled, OnDisabled, etc. But yeah, seems like undocumented feature.

    Actually this post pointed me into the right direction:
    https://answers.unity.com/questions/235514/execute-code-when-component-added.html

    As for overriding Unity's events - what's giving you trouble; overriding functionality or the inspector drawing?
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    12
    I knew about MonoBehaviour.Reset. However, Editor.Reset is undocumented, and gets called on all Editors for a GameObject when a new component gets added or removed. It is awesome and I'm glad we discovered it. I'm just wondering why it isn't documented and why it works.

    In regards to Unity events, I'm referring to the editor's engine-side event handling. Basically what is happening is in the rectangle that covers the component name instead of putting the folder name, I tried putting other UI icons such as foldouts and checkboxes. They do not respond at all. Instead the ComponentFolder collapses and expands as normal. I tried adding debugging messages, checking mouse events and using them if they occur inside the rectangle, and several other techniques. It's not even that both my icons and the Unity interface are both responding to the events. It's more like Unity eats the events before they even propagate to my code.

    It's not a huge issue if I can draw all the components inside a master component automatically, which Editor.Reset lets me do. But I feel it isn't as clean. Oh well.
     
  8. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    329
    Yeah, a master component approach seems like the way to go. Perhaps you could create a specialized EditorWindow that mimicks the inspector, but only for your master component. That way you can customize the layout how you like, without normal components interfering. You could even have something like Windows style folder icons, which you can double click to expand. Sounds fun, good luck!