Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Using EditorGUI in Editor class

Discussion in 'Scripting' started by RoeeHerzovich, Feb 3, 2021.

  1. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    I made a Custom Property Drawer in order to avoid manually settings positions and boxes for EditorGUI, and tbh.. I am very happy with my result, so happy in fact that when I got into editing Editors(and not properties) and I used the EditorGUILayout, I was kinda disappointed...
    I know I can extend on the EditorGUILayout just like I have before, but it's way harder to control due to it already being a wrap, it's possible but a pain. And EditorGUI doesn't work properly in editors nor is my extension for it...

    My question is, how can I make EditorGUI work properly there, and why is there even an EditorGUILayout

    I mean.. and if it already exists.. why using EditorGUI for properties and EditorGUILayout for editors..

    This whole concept is confusing...
    When I tried using EditorGUI the spacing was weird, the box heights were weird, and overall the entire thing was kinda... broken..

    Tho in property drawers it's working beautifully well.

    Also if anyone wants my extension... I can send a file, the reason I don't here is because it's approximatly.. 800+ lines(yes I wrote it in one day lol)
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    There's some number of posts going around reporting formatting errors in the package manager layout. It seems related to some complexity in the way the package manager UI package chunks are assembled not playing well with a recent video update, but google yourself. Is it possible the issues with box heights in your code is the same issue?
     
  3. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    When I tried to google, what's EditorGUILayout for, why each is used for different things and why EditorGUI doesn't work in Editor I didn't really get many answers...

    Idk what is my height Issue in there...

    In my EditorGUI extension, I have a method called Label(), you can imagine what it does.
    While it works perfectly well when used in property drawers doing Label() in the Editor with this drawer causes some weight height changes to happen.

    I just had this in mind:
    In my EditorGUI extension, I keep a height parameter and when doing a space I increase it and change my rect, but EditorGUILayout works on EditorGUILayout.Space(), thus nothing is being spaced. I assume that is why the problem is happening.

    But... how will I know when to use my spacing and when to use EditorGUILayout.Space()? Unless.. I make an EditorGUILayout extension which I try to avoid...

    EDIT

    If anyone wants my extension(again, made it in one day mostly during the night and it's still not finalized soo prob gonna have a few bugs soo.. yeah)
     

    Attached Files:

    Last edited: Feb 3, 2021
  4. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103

    So I managed to make it support an Editor too... almost..
    It does regular spacing right and everything... BUT

    if there's a property that has a foldout(for example an array)
    It wouldn't add the height of this property as an open one..

    It works for my property drawers tho..
    Here's the code:


    Code (CSharp):
    1.         public void Property(SerializedProperty property, bool includeChildren = false)
    2.         {
    3.             EditorGUIUtility.labelWidth = GetWidth(property.displayName);
    4.             EditorGUI.PropertyField(PLine, property, includeChildren);
    5.             Space();
    6.         }
    7.  
    8.  
    9.  
    10. /// Space()
    11.  
    12.         public void Space(float space = 1f)
    13.         {
    14.             if (layout)
    15.                 EditorGUILayout.Space(lineHeight * space);
    16.             else
    17.                 height += lineHeight * space;
    18.  
    19.             line = line.Down(lineHeight * space);
    20.         }
    21.  
    22. // Line.Down()
    23.  
    24.         public static Rect Down(this Rect rect, float height) => new Rect(rect.x, rect.y + height, rect.width, rect.height);
    25.  
    26. // PLine
    27.  
    28.         private Rect PLine => Line.XPortion(indent == 0 ? 0 : -14);
    29.  
    30. // Line
    31.  
    32.         private Rect Line
    33.         {
    34.             get
    35.             {
    36.                 var l = line.XPortion(indent * 20);
    37.                 return new Rect(l.x, l.y, l.width - (4 * indent + 2), l.height);
    38.             }
    39.         }
    40.  
    41. // Rect.XPortion
    42.  
    43.         public static Rect XPortion(this Rect rect, float x) => new Rect(rect.x + x, rect.y, rect.width - x, rect.height);

    EDIT

    I think it's because the space spaces one line instead of a height, I need to check for the property's height and add by it

    EDIT II
    I did it, it maintains the property drawer's functionality while opening height in the Editor, HOWEVER, it opens WAY more height than it should while using the exact same variables, which leads me to think EditorGUILayout.Space()'s spacing ratio could be difference than mine..

    Here's the new property code:


    Code (CSharp):
    1.         public void Property(SerializedProperty property, bool includeChildren = false)
    2.         {
    3.             EditorGUIUtility.labelWidth = GetWidth(property.displayName);
    4.             EditorGUI.PropertyField(PLine, property, includeChildren);
    5.             Space( EditorGUI.GetPropertyHeight(property) / lineHeight);
    6.         }
    EDIT III
    It was most likely a bug because now it works perfectly well... I can't explain why.. same code works good
     
    Last edited: Feb 3, 2021
    Kurt-Dekker likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Awesome. Editor code can be tricky, and I wouldn't rule out some bugs in the metrics calculations either. Glad you're operational!
     
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    Yep, the IMGUI system can be a bit tricky, but once you understand it, it's quite simple. Though specifically custom editors and the inspector window has some additional quirks you should be aware of.

    For the IMGUI system in general I would recommend my IMGUI crash course as reference (which is just a mirror of my UA answer over here).

    The layout system is just a wrapper around the normal GUI methods. They just request rects from the layout system depending on the content and the provided GUILayoutOptions and then just call the corresponding GUI version.

    The main issues with the inspector is that all the systems we have now have evolved over years. Things like PropertyDrawers and SerializedObject / SerializedProperty didn't exist in the beginning. UT worked hard to improve the performance of the Unity editor. Currently the inspector does not really use the layout system by default but they implemented some optimised GUI block on the native side which seems to cache certain things. So inside a property drawer you usually do not get the Layout event, that's why GUILayout does not work properly in a property drawer. Now comes the strangest thing. Once you implement a custom editor for your class (no special code, just call to DrawDefaultInspector), now the property drawers will receive the Layout event because the actual processing of the property drawer is now routed through the custom editor which does receive the Layout event.

    Though PropertyDrawers should in general not use any layout event stuff as it's completely based on rects that are given from the outside. A PropertyDrawer can change the height of its own rect by overriding the GetPropertyHeight method.

    The individual editors that are displayed in the inspector window are just sub sections of the Inspector window. Coordinate system wise property drawers as well as custom editors draw relative to the inspector window top left corner. CusomEditors do not receive any predetermined rect, so you usually have to use the layout system if you write one. Keep in mind that GUILayout.Space is not always a vertical space. It depends on the current layout group. If you're inside a horizontal group the spacing would be horizontal.

    If you want to change or specify the size of a layouted control, you generally use GUILayoutOptions as optional parameters. I thought I explained them more in detail in my crash course but I just explained the basics. The GUILayout class has several methods to create GUILayoutOptions. Specifically Width, Height, MinWidth, MinHeght, MaxWidth, MaxHeight, ExpandWidth and ExpandHeight. They essentially overwrite the settings dictated by the GUIStyle. They can simply be used as the last parameter(s) of any GUILayout or EditorGUILayout control function.

    Note when you want to draw a SerializedProperty in your own custom editor and you don't want to use the layout system, you have to call GetPropertyHeight yourself since you have to provide the rect for the drawer.

    In essence you can use GUI / EditorGUI in a custom editor, but you would need at least one layout rect which you base everything else on. So you can just use one variant of the GetRect method and use that for your normal GUI / EditorGUI stuff.
     
    Kurt-Dekker likes this.
  7. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103

    Thank you for your answer!

    I was aware that Layouts shouldn't be used in property drawers, but I didn't know why, it's useful, and as for my extension, it is almost fully functional in Editor as well, for now, I still have a few indenting issues but it's no biggies to fix once I focus on it. Another interest of mine is in the EditorGUILayout.BeginVertical, or more specifically, in the Texture you can put. I managed to manually put textures on rects, no problem. but due to me measuring the rect BY the properties inside, I obviously draw it after the properties are drawn, thus on top of them... How do they make it so that the texture will be drawn in the background?
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    I think you do that with two passes: pass 1 to "pretend" to lay everything out, then draw the texture, then pass 2 skips drawing the texture but does the text items.

    That is unless I'm misunderstanding the nature of your question and how layout state is affected by your actions...
     
  9. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103

    When you use EditorGUILayout.BeginVertical("helpBox")

    Whenever you do EditorGUILayout.EndVertical(); it surrounds the entire area with this helpBox.

    I tried to simulate that, I can get the entire rectangle but only after the end function is called, which means all the drawing of properties that are inside the box are already drawn, which makes the texture be drawn on top of them instead of below...

    I wonder how they managed to make the texture be drawn below the already-drawn properties...
     
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    Well, you haven't really read through my crash course, right? ^^ All controls are drawn using a GUIStyle which are either stored in the current GUISkin or, especially for some editor styles, are just stored / managed in some internal static classes. A GUIStyle in general can have different background images depending on the state. That's how buttons draw their background and also show different background images depending on the mouse over state or the active state (when the mouse button is hold down). The GUIStyle is the heart of the IMGUI system, at least the visual part.

    About how the background is drawn under the properties: It isn't. It's drawn before the properties. Keep in mind that every event that the OnGUI / OnInspectorGUI method is processing is paired with an additional Layout event which is executed right before the actual event. The Layout event is used to collect information about all controls that should be drawn so when the actual event is processed the layout system knows all the sizes required. That's why it's vital that there are no structural changes between the layout event and the actual event that is processed.

    Note that "helpBox" is just a GUIStyle. The GUIStyle class has an implicit conversion operator that can "convert" a string into a GUIStyle by looking it up in the currently active GUISkin that is referenced by the static GUI.skin property.

    I have the feeling just explaining everything I explained in the crash course again.
     
    RoeeHerzovich likes this.
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Is it something as hacky as
    Space( -100);
    ... :)
     
  12. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    Why use IMGUI? UIElements/UIToolkit has been built into the core for making inspectors/editor windows since 2018 I believe. Using it for runtime can be quite buggy, but fine for editors and you don't need any package to use it in the editor only.

    You use UIBuilder(buggy still but it works) to visually author a custom inspector or editor window. You then use the resulting uxml file in the Editor/EditorWindow classes to tell it what to display. After that you can hook up the logic for buttons and any other field changes you need to react to using the query system on the root element to get the elements you want. Some people have a problem with binding, but it's not hard to do, even through UIBuilder.

    In the future the entire editor will be rendered using UITK, most of the newer editor windows are, like the Package Manager, and Project Settings. I think the only things holding them back is a generic editor and people still holding on to IMGUI.
     
  13. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    Yes, but according to the UI overview UIElements is still under development and therefore not a finished polished feature. I have not tracked the development recently. However we already had a lot of questions from people with all sorts of issues with the new UI tool kit. Apart from that UIElements may be great for large scale editor extentions. However for simple custom editors or property drawers it's almost overkill. Also as a programmer I personally perfer creating editor tools with code rather than some WYSIWYG approach.

    Don't get me wrong, UIElements is a long missing UI approach and it fills some of the gaps between uGUI and the IMGUI system. Though they are not really replacements for each other. UIElements also have an IMGUI container as element, so you can be quite sure that IMGUI will stay. It's with the Unity editor for about 10 years now.

    Yes, major parts of the editor have been redesigned with UIElements, however it's not really ready for production. Just recently some people had rendering issues with the package manager. There are also quite a few developers which may be stuck on an older Unity version due to project restrictions. So always grabbing the newest shiny tech is not always the best choice ^^.
     
  14. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    The whole not ready for production is for runtime support. The editor side is good to go and yes is overkill when using uxml to design smaller custom editors or property drawers, but you can still do it 100% code with no uxml or uss involved.

    As a programmer myself I write the smaller editors and property drawers 100% code. It took me almost a month to design my item database editor in IMGUI, took about 3 hours to convert to UITK with far less code so its actually easier to debug. So it is a total win with larger editor windows to be visually authored. A lot of the little components I had to do in uxml/uss because of visual consistency, so it did seem like overkill. There is also the problem if I want to turn that into a package, how it currently works with absolute pathing. It can break things quickly if the user changes directory structure.

    I don't think IMGUI will ever go away, backwards compatibility is important to a vast majority of people. It's also nice to quickly setup some debug displays in OnGUI, not as easy with UITK.

    I do agree that each system cannot completely replace the other, and trying to have one replace them all is not an easy task. It's quite plagued with growing pains.

    They keep changing the package manager's layout and that can break things, with any system.

    I think the main problems with UITK for most part is inconsistencies in the API, the cross over from editor to runtime and documentation.

    They need to stick to one paradigm for events instead of how it currently is. Some complain about the binding system, I don't have an issue with that myself but I never tried custom binding, yet.

    They keep making changes to the core component to get runtime to work, which breaks it in the editor.

    The documentation is... well its... lacking to say the least. It doesn't give much in the way of showing you how to code the UI, it instead pushes uxml/uss and how to handle that. The docs on drag and drop only work in the editor, so there is no runtime solution other than to roll your own, but does it mention this? No, in fact if you try to use the events it gives you errors when you try to build your project.

    In the end more people using it means more people finding bugs and hopefully filing bug reports, which hopefully leads to a more stable product. I was able to create a card game that uses UITK entirely, and in the process I found bugs and filed reports and a lot of them actually got fixed.

    It's ultimately up to the OP to use what they want. I never really liked IMGUI, still used it and banged my head over how to get it to work how I wanted, and I do the same with UITK or any other system I haven't used before.
     
    Bunny83 likes this.
  15. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    Btw if anyone's wondering how the final result looks like(I changed the code a bit since I uploaded the file), here's an example of a class I made:
    (Image)

    The content is not important, but the design. Do you find it decent?

    Also, this is the code that draws it:

    Code (CSharp):
    1.         public override void Draw(LayoutDrawer d)
    2.         {
    3.             bool editor = Application.isEditor;
    4.             bool started = Application.isPlaying;
    5.  
    6.             editorOpen.boolValue = d.Foldout($"{entityName.stringValue}(Entity)", editorOpen.boolValue, FontStyle.Bold);
    7.  
    8.             if (editorOpen.boolValue)
    9.             {
    10.                 d.BeginVertical();
    11.                 d.Property(entityName, "Name", 0, 0.75f);
    12.                 float n = d.Label(" | ", EditorStyles.boldLabel, 0.76f);
    13.                 d.Label($"ID[{id.intValue}]", n, true);
    14.  
    15.                 d.Space();
    16.  
    17.                 editorDamagableOpen.boolValue = d.Foldout("Damagable", editorDamagableOpen.boolValue, FontStyle.Bold);
    18.  
    19.                 if (editorDamagableOpen.boolValue)
    20.                 {
    21.                     d.BeginVertical();
    22.                    
    23.                     EditorGUI.BeginChangeCheck();
    24.                     d.MinProperty(maxHealth, 0, 0, 0.75f);
    25.                     if (editor && !started && EditorGUI.EndChangeCheck())
    26.                         health.floatValue = maxHealth.floatValue;
    27.  
    28.                     n = d.Label(" | ", EditorStyles.boldLabel, 0.76f);
    29.                     d.Label($"Health: {health.floatValue}", n, true);
    30.                     d.Space(0.125f);
    31.  
    32.                     d.ProgressBar(health.floatValue, maxHealth.floatValue, $"Health({health.floatValue} / {maxHealth.floatValue})", Color.red);
    33.  
    34.                     d.Space(0.5f);
    35.  
    36.                     d.Property(hasRegen);
    37.  
    38.                     d.Enabled = hasRegen.boolValue;
    39.                     d++;
    40.  
    41.                     d.MinProperty(regenRate, 0);
    42.                     d.Property(regenCD, "Regen Cooldown");
    43.  
    44.                     d--;
    45.                     d.Enable();
    46.  
    47.                     d.EndVertical();
    48.                     d.Space(0.5f);
    49.                 }
    50.  
    51.                 d.Space(0.5f);
    52.  
    53.                 editorInterfaceOpen.boolValue = d.Foldout("Interfaces", editorInterfaceOpen.boolValue, FontStyle.Bold);
    54.  
    55.                 if (editorInterfaceOpen.boolValue)
    56.                 {
    57.                     d++;
    58.                     d.Label("hello!");
    59.                     d--;
    60.                 }
    61.  
    62.                 d.EndVertical();
    63.             }
    64.         }
    I made an extension over the Editor class and Draw() is called via OnInspectorGUI(), also OnInspectorGUI() is sealed and Draw() is abstract
     

    Attached Files:

    • IMG.PNG
      IMG.PNG
      File size:
      30.7 KB
      Views:
      270
    Kurt-Dekker likes this.