Search Unity

Property Drawers

Discussion in 'UI Toolkit' started by MartinIsla, Dec 9, 2018.

  1. MartinIsla

    MartinIsla

    Joined:
    Sep 18, 2013
    Posts:
    104
    Hello!

    I was wondering if there's a (new? better?) way to implement property drawers using UIElements instead of the good ol' IMGUI.

    Thanks!
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Declaring a custom PropertyDrawer in UIElements is very similar to how it worked with IMGUI. Here's an example (requires 2019.1.0a10):

    Code (CSharp):
    1. [Serializable]
    2. public class UIElementsDrawerType
    3. {
    4.     public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
    5.     public string name;
    6.     public int amount = 1;
    7.     public IngredientUnit unit;
    8. }
    9.  
    10. [CustomPropertyDrawer(typeof(UIElementsDrawerType))]
    11. public class UIElementsCustomDrawer : PropertyDrawer
    12. {
    13.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    14.     {
    15.         var container = new VisualElement();
    16.         UnityEngine.Random.InitState(property.displayName.GetHashCode());
    17.         container.style.backgroundColor = UnityEngine.Random.ColorHSV();
    18.  
    19.         { // Create drawer using C#
    20.             var popup = new PopupWindow();
    21.             container.Add(popup);
    22.             popup.text = property.displayName + " - Using C#";
    23.             popup.Add(new PropertyField(property.FindPropertyRelative("amount")));
    24.             popup.Add(new PropertyField(property.FindPropertyRelative("unit")));
    25.             popup.Add(new PropertyField(property.FindPropertyRelative("name"), "CustomLabel: Name"));
    26.         }
    27.  
    28.         { // Create drawer using UXML
    29.             var vsTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/Bindings/custom-drawer.uxml");
    30.             var drawer = vsTree.CloneTree(property.propertyPath);
    31.             drawer.Q<PopupWindow>().text = property.displayName + " - Using UXML";
    32.             container.Add(drawer);
    33.         }
    34.  
    35.         return container;
    36.     }
    37. }
     
  3. mentorgame1

    mentorgame1

    Joined:
    Oct 31, 2016
    Posts:
    19
    thank's i will use that on my property drawers.
     
  4. MartinIsla

    MartinIsla

    Joined:
    Sep 18, 2013
    Posts:
    104
    uDamian I'll just say you made my entire tool
     
  5. MartinIsla

    MartinIsla

    Joined:
    Sep 18, 2013
    Posts:
    104
    Is it possible this is broken in 2019.1.0a12?
    When doing this (overriding the CreatePropertyGUI method), I get the "No GUI implemented" message in the place of the property. Added Debug.Logs inside and they don't show up. Overriding the OnGUI method works!


    EDIT: I read the docs and that makes sense in the inspector, presumably because it uses IMGUI. However, in the EditorWindow, fully made with UIElements, it's like it's not there at all!

    EDIT 2: I think it's PropertyFields that are broken. I can't get them to display anything (even integers or Vectors).
     
    Last edited: Dec 24, 2018
    KyryloKuzyk likes this.
  6. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    There is a bit of a catch here, indeed. You need to make sure the Inspector (Editor) for your object is also using UIElements for any UIElements-based custom PropertyDrawers to show up.

    That is, you need to make sure to override CreateInspectorGUI() on the Editor class for your object and then populate manually with PropertyFields. This is a temporary situation until we switch the default inspectors of all objects to UIElements (right now default inspectors still use IMGUI).

    I double checked our UIElements PropertyFields and they appear to work. If they don't work for you, even in a standalone EditorWindow, definitely submit a bug.
     
    Flying_Banana likes this.
  7. johnseghersmsft

    johnseghersmsft

    Joined:
    May 18, 2015
    Posts:
    28
    Any idea when UIElements-based Inspectors are going to be the default? It's a pain to have to manually write an editor for every class in which a custom type--with UIElements-based PropertyDrawers--are used.

    Also, in any such class, if you want to use the default (IMGUI-based) implementation for the non-custom stuff, you need to tag any such properties as [HideInInspector] so you don't get the "No GUI implemented" message.
     
  8. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    I don't have an ETA on when the Inspectors will switch to UIElements by default. We stayed with IMGUI for now to reduce the disruption when we switched the core of the InspectorWindow to UIElements. I'll bump this task a bit in our plans if I can. Thanks for the mention.

    For UIElements custom inspector classes, once you declare it, you'll have to build the entire inspector UI yourself. I'm not sure how you're selecting some properties to be automatically generated without an explicit use of PropertyField.

    Also, [HideInInspector] just hides the property from being display in default inspectors. As soon as you define a custom one, that property doesn't do much.

    Can you maybe add a snippet of one of your custom inspectors showing the problem?
     
  9. johnseghersmsft

    johnseghersmsft

    Joined:
    May 18, 2015
    Posts:
    28
    Here's an Editor for a little test class that does this. "EventToCatch" is a custom event name type (I mentioned that type in another thread). It has a property drawer written for UIElements.

    Code (CSharp):
    1.     [CustomEditor(typeof(DestroyOnEvent))]
    2.     public class DestroyOnEventEditor : Editor
    3.     {
    4.         public override VisualElement CreateInspectorGUI()
    5.         {
    6.             var container = new VisualElement();
    7.  
    8.             // Draw the legacy IMGUI base
    9.             var imgui = new IMGUIContainer(OnInspectorGUI);
    10.             container.Add(imgui);
    11.  
    12.             // Create property fields.
    13.             // Add fields to the container.
    14.             container.Add(
    15.                      new PropertyField(serializedObject.FindProperty("EventToCatch")));
    16.             return container;
    17.  
    18.         }
    19.  
    20.         public override void OnInspectorGUI()
    21.         {
    22.             DrawDefaultInspector();
    23.         }
    24.  
    25.     }
    26.  
    The property in the class is:
    Code (CSharp):
    1.         [HideInInspector]
    2.         public UiEventTag EventToCatch;
    3.  
    In this case, the IMGUI portion is really only displaying the script name, but this works to display all non-UIElements PropertyDrawers in the class.

    Unfortunately, if [HideInInspector] is not use, the default Inspector shows "No GUI Implementation".

    I've got a workaround that I'll post as another message with a base class that enumerates hidden properties that have appropriate drawers.
     
  10. johnseghersmsft

    johnseghersmsft

    Joined:
    May 18, 2015
    Posts:
    28
    As a workaround, since I need a custom editor for any class that uses these new PropertyDrawers, I've created a base class from which a custom Editor can be created. I've also added an Attribute to mark compatible PropertyDrawers. One could theoretically probe the drawer class to see if it overrides CreateInspectorGUI() instead.

    Either of these requires that I be able to find the class type of a PropertyDrawer for a given property type. For that, I use reflection to gain access to ScriptAttributeUtility.GetDrawerTypeForType().

    Code (CSharp):
    1. using System;
    2. using System.Reflection;
    3. using UnityEditor;
    4. using UnityEditor.UIElements;
    5. using UnityEngine.UIElements;
    6. using UnityEngine;
    7.  
    8. namespace PluginUtils
    9. {
    10.     [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
    11.     public class VEPropertyDrawerAttribute : Attribute
    12.     {
    13.  
    14.     }
    15.  
    16.     public class VisualElementPropertyDrawerEditorBase : Editor
    17.     {
    18.         public override VisualElement CreateInspectorGUI()
    19.         {
    20.             var container = new VisualElement();
    21.  
    22.             // Draw the legacy IMGUI base
    23.             var imgui = new IMGUIContainer(OnInspectorGUI);
    24.             container.Add(imgui);
    25.  
    26.             // Find all properties that are marked [HideInInspector] that have
    27.             // a PropertyDrawer tagged with the [VEPropertyDrawer] attribute and create
    28.             // PropertyFields for each of them.
    29.             var type = target.GetType();
    30.             // Create property fields.
    31.             // Add fields to the container.
    32.             CreatePropertyFields(container, type);
    33.             return container;
    34.  
    35.         }
    36.  
    37.         protected void CreatePropertyFields(VisualElement container, Type objectType)
    38.         {
    39.             var fields = objectType.GetFields(
    40.                   BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public);
    41.             foreach (var fieldInfo in fields)
    42.             {
    43.                 var attr = fieldInfo.GetCustomAttribute<HideInInspector>();
    44.                 if (attr == null || !IsPropertyDrawerTagged(fieldInfo.FieldType))
    45.                     continue;
    46.  
    47.                 container.Add(
    48.                     new PropertyField(serializedObject.FindProperty(fieldInfo.Name)));
    49.             }
    50.         }
    51.  
    52.         protected bool IsPropertyDrawerTagged(Type propertyType)
    53.         {
    54.             var drawerType = GetPropertyDrawerType(propertyType);
    55.             if (drawerType == null)
    56.                 return false;
    57.  
    58.             var attrs = drawerType.GetCustomAttributes(
    59.                                 typeof(VEPropertyDrawerAttribute), true);
    60.             return attrs.Length > 0;
    61.         }
    62.  
    63.  
    64.         /// <summary>
    65.         /// Use Reflection to access ScriptAttributeUtility to find the
    66.         /// PropertyDrawer type for a property type
    67.         /// </summary>
    68.         protected Type GetPropertyDrawerType(Type typeToDraw)
    69.         {
    70.             var scriptAttributeUtilityType = GetScriptAttributeUtilityType();
    71.  
    72.             var getDrawerTypeForTypeMethod =
    73.                         scriptAttributeUtilityType.GetMethod(
    74.                             "GetDrawerTypeForType",
    75.                             BindingFlags.Static | BindingFlags.NonPublic, null,
    76.                             new[] { typeof(Type) }, null);
    77.  
    78.             return (Type) getDrawerTypeForTypeMethod.Invoke(null, new[] { typeToDraw });
    79.         }
    80.  
    81.         protected Type GetScriptAttributeUtilityType()
    82.         {
    83.             var asm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(),
    84.                                               (a) => a.GetName().Name == "UnityEditor");
    85.  
    86.             var types = asm.GetTypes();
    87.             var type = Array.Find(types, (t) => t.Name == "ScriptAttributeUtility");
    88.  
    89.             return type;
    90.         }
    91.         public override void OnInspectorGUI()
    92.         {
    93.             DrawDefaultInspector();
    94.         }
    95.  
    96.     }
    97. }
    98.  
    This allows me to create a custom editor like this:
    Code (CSharp):
    1.     [CustomEditor(typeof(DragDetector))]
    2.     public class DragDetectorEditor : VisualElementPropertyDrawerEditorBase
    3.     {
    4.     }
    And the PropertyDrawer is Attribute-tagged as:
    Code (CSharp):
    1.     [VEPropertyDrawer]
    2.     [CustomPropertyDrawer(typeof(Tag), true)]
    3.     public class TagPropertyDrawer : PropertyDrawer
    4.  
     
    Stardog and Grhyll like this.
  11. johnseghersmsft

    johnseghersmsft

    Joined:
    May 18, 2015
    Posts:
    28
    I realized that the better way to detect a VisualElements-based drawer was indeed to look for an implementation of CreatePropertyGUI() declared on the PropertyDrawer (not inherited). So, I replaced these lines in bool IsPropertyDrawerTagged(Type propertyType):
    Code (CSharp):
    1. var attrs = drawerType.GetCustomAttributes(typeof(VEPropertyDrawerAttribute), true);
    2. return attrs.Length > 0;
    with
    Code (CSharp):
    1. var method = drawerType.GetMethod("CreatePropertyGUI",
    2.                 BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance, null,
    3.                 new[] { typeof(SerializedProperty) }, null);
    4. return method != null;
    5.  
    Works like a charm and I no longer need the tagging attribute.
     
  12. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    I agree this is not ideal. I'll try to bump the priorities a bit on the UIElements default inspector. Thanks for the feedback and in-depth workaround!
     
  13. CyRaid

    CyRaid

    Joined:
    Mar 31, 2015
    Posts:
    134
    John, may I use your code for now until default inspector is UI Elements?
     
  14. johnseghersmsft

    johnseghersmsft

    Joined:
    May 18, 2015
    Posts:
    28
    Feel free to use it!
     
  15. iSinner

    iSinner

    Joined:
    Dec 5, 2013
    Posts:
    201
    @uDamian is there a way to tell if the default inspector is drawn via UIElements or IMGUI?
     
  16. larsolm5853

    larsolm5853

    Joined:
    Oct 24, 2017
    Posts:
    21
    Here's my default editor that emulates the normal inspector but draws everything with UIElements and allows you to make normal custom property drawers.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using UnityEngine.UIElements;
    5. using UnityEditor.UIElements;
    6.  
    7. [CustomEditor(typeof(Object), true, isFallback = true)]
    8. public class DefaultEditor : UnityEditor.Editor
    9. {
    10.     public override VisualElement CreateInspectorGUI()
    11.     {
    12.         var container = new VisualElement();
    13.  
    14.         var iterator = serializedObject.GetIterator();
    15.         if (iterator.NextVisible(true))
    16.         {
    17.             do
    18.             {
    19.                 var propertyField = new PropertyField(iterator.Copy()) { name = "PropertyField:" + iterator.propertyPath };
    20.  
    21.                 if (iterator.propertyPath == "m_Script" && serializedObject.targetObject != null)
    22.                     propertyField.SetEnabled(value: false);
    23.  
    24.                 container.Add(propertyField);
    25.             }
    26.             while (iterator.NextVisible(false));
    27.         }
    28.  
    29.         return container;
    30.     }
    31. }
    32.  
     
  17. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    It all depends where you want to know this. I haven't tried this but if you want to determine this from inside a custom PropertyDrawer, you can implement both OnGUI() and CreatePropertyGUI(). If the OnGUI() call is run, you know you're inside an IMGUI-based Inspector and maybe put out a message saying this is not supported.

    Thanks for sharing!
     
  18. iSinner

    iSinner

    Joined:
    Dec 5, 2013
    Posts:
    201
    From inside unity editor, w/o writing any code to test the waters. If i open unity editor and look at the inspector, is there any way that unity provides out of the box to tell how is it drawn, via IMGUI or UIElement? i know there is a UIElement Debugger, can that help me in any way?

    The question is in the context of different unity versions, i have no idea when unity transitions its inspector drawing from IMGUI to UIElement, and i would like to know exactly which version of unity what methodology uses to draw its inspector.
     
  19. Grhyll

    Grhyll

    Joined:
    Oct 15, 2012
    Posts:
    119
    Thank you so much for this!
    And... would you happen to know how I could assign m_FieldInfo on my newly found PropertyDrawer? I guess there's a way with the kind of complicated reflection you've used to get ScriptAttributeUtility, but that's a subject I don't know anything about yet :/
    (Saw the existence of this here: https://github.com/Unity-Technologi...or/Mono/ScriptAttributeGUI/PropertyHandler.cs )
     
  20. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    To answer your specific question first, you can use the UIElements Debugger and try to "Pick" an Editor inside the InspectorWindow. If you can inspect the various fields, it's using UIElements, but if the entire Editor (component) is one big "element", then it's using IMGUI (inside an IMGUIContainer element). The InspectorWindow itself (like the headers and the individual Editor frames) is already using UIElements. It just puts any IMGUI Editors inside IMGUIContainers.

    That said, there will not be a specific version of Unity to that completely switches all inspectors to UIElements. IMGUI will probably stick around for a long time. Unless you're referring to the default generic auto-generated inspectors for objects that have no custom inspector defined - then yes, this will switch at some point in the future in a specific version. But we will very likely announce this.
     
    iSinner likes this.
  21. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,537
    Just wanted to pitch in that UIE default Inspectors would be very nice!

    Building custom drawers and tooling in general seems restricted to operating inside UIE spaces, whereas this is not a problem if everything were built in IMGUI it could work everywhere, thus making it hard to choose UIE.

    UIE brings a lot to the table and I suppose this is still a 'transitional' time but I'm not sure how to build good tooling that works in custom UIE windows as well as the default inspectors and that seems like a big drawback that is still going to exist well into 2020.x
     
  22. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Fully agree. We're working on removing this drawback but yes, it will be with us for a while longer.

    In the meantime, if you already know a lot about how IMGUI works and a little about how UIElements works, it is..theoretically possible to embed UIE into IMGUI. The idea is to just draw UIE on top of an empty IMGUI rect and use the UIE GeometryChangeEvents to keep the IMGUI rect in sync. I'm not saying we're working on this nor that we can officially support the use case, but if you really need it, it may be possible. :)
     
    LaneFox likes this.
  23. Grhyll

    Grhyll

    Joined:
    Oct 15, 2012
    Posts:
    119
    Just in case anyone would end up on my post with the same issue, I finally managed to do it (and it was a lot simpler than expected, at least a lot simpler than the other things done in this thread):
    Code (CSharp):
    1.  
    2.             PropertyDrawer result = (PropertyDrawer)System.Activator.CreateInstance(propertyDrawerType);
    3.            
    4.             var prop = result.GetType().GetField("m_FieldInfo", System.Reflection.BindingFlags.NonPublic
    5.     |           System.Reflection.BindingFlags.Instance);
    6.             prop.SetValue(result, fieldInfo);
     
    uDamian likes this.
  24. fherbst

    fherbst

    Joined:
    Jun 24, 2012
    Posts:
    802
    I'm currently creating property drawers for a List of ScriptableObjects.
    While it's easy to have the drawer show the contents of the ScriptableObjects, I don't seem to be able to make an good ol' ObjectField that references the ScriptableObject. (I would expect that's like "DrawDefaultInspector" but for a property).

    Trying to create a new PropertyField from the property that goes into CreatePropertyGUI just gives me a StackOverflow, as it is infinitely trying to use the Drawer. How can I, from a PropertyDrawer, draw an ObjectField to the "original"?

    EDIT: as usual after asking in the forum I had the right idea, so for future ref:

    Code (CSharp):
    1. var propField = new ObjectField();
    2. propField.objectType = typeof(MyScriptableObject);
    3. propField.value = property.objectReferenceValue;
    4. v.Add(propField);
     
    uDamian likes this.
  25. Starducky

    Starducky

    Joined:
    Aug 1, 2015
    Posts:
    5
    I'm having trouble writing a PropertyDrawer for a ScriptableObject:

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(MyScriptableObject), true)]
    2. public class MyScriptableObjectDrawer : PropertyDrawer {
    3.     public override VisualElement CreatePropertyGUI(SerializedProperty property) {
    4.         var container = new VisualElement();
    5.  
    6.         if (property.objectReferenceValue == null) {
    7.             return container;
    8.         }
    9.         SerializedObject so = new SerializedObject(property.objectReferenceValue);
    10.  
    11.         var iterator = so.GetIterator();
    12.         if (iterator.NextVisible(true)) {
    13.             do {
    14.                 var propertyField = new PropertyField(iterator.Copy()) { name = "PropertyField:" + iterator.propertyPath };
    15.  
    16.                 if (iterator.propertyPath == "m_Script")
    17.                     propertyField.SetEnabled(value: false);
    18.  
    19.                 container.Add(propertyField);
    20.             }
    21.             while (iterator.NextVisible(false));
    22.         }
    23.         return container;
    24.     }
    25. }
    In the UIElements debugger, the PropertyFields are there but they are empty - it seems like the bindings aren't working correctly. Any ideas?
     
  26. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    I can't say for sure why your fields are not binding. It's good to confirm the property paths given are correct. If you know IMGUI well, you can try making the IMGUI version of this PropertyDrawer with the same property paths. If IMGUI binds but UIElements doesn't, that's definitely a bug.

    One thing to not is that you don't need the
    iterator.Copy()
    copy. All that
    PropertyField
    's constructor does is it saves the
    propertyPath
    string to be used when the recursive
    Bind()
    runs over your UI tree. It does not keep a reference to the actual PropertyField. So you can safely pass just
    iterator
    . It does this so that you can repeatedly Bind() and Unbind() your UI without keeping references to the
    SerializedObject
    .
     
    Starducky likes this.
  27. aybe

    aybe

    Joined:
    Feb 20, 2019
    Posts:
    55
    I have followed @pirho_luke instructions to implement a default editor, now my property drawer using UIElements shows but there's no property label at all :(

    Unity_2020-01-22_06-37-35.png

    How do I fix this ?

    This is the UXML I load in CreatePropertyGUI:

    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    2.     <ui:VisualElement style="padding-left: 2px; padding-right: 2px;">
    3.         <Style src="RangeSlider.uss" />
    4.         <ui:MinMaxSlider picking-mode="Ignore" min-value="-17.1" max-value="12" low-limit="-10" high-limit="40" name="MinMaxSlider" class="min-max-slider" />
    5.         <ui:VisualElement style="flex-direction: row; justify-content: space-between;">
    6.             <ui:Label text="Label" name="MinLabel" class="min-max-label" />
    7.             <ui:Label text="Label" name="MaxLabel" class="min-max-label" />
    8.         </ui:VisualElement>
    9.     </ui:VisualElement>
    10. </ui:UXML>
    11.  
    And I load it this way:

    Code (CSharp):
    1. public override VisualElement CreatePropertyGUI(SerializedProperty property)
    2. {
    3.     var element = new VisualElement();
    4.     var asset   = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/Controls/RangeSlider.uxml");
    5.     var tree    = asset.CloneTree(property.propertyPath);
    6.     element.Add(tree);
    7.     return tree;
    8. }
    Thanks :)
     
  28. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    First, change this:
    var tree    = asset.CloneTree(property.propertyPath);

    to:
    var tree    = asset.CloneTree();

    CloneTree(string bindingPath) is a short hand for:
    Code (CSharp):
    1. var tree = asset.CloneTree();
    2. tree.bindingPath = path;
    and it's only for UXML templates that describe a bound type (like a struct), with fields for each member of the struct with a sub-binding-path. That's not what I see in your UXML (and it's a little more complexity than you need for your example).

    That said, I see in the Inspector screenshot exactly what I should given your UXML file. When you create a Custom PropertyDrawer, you take over the entire UI for it, including the property label. This was the case for IMGUI as well. If you want a label there, you can create a VisualElement container that uses "flex-direction: row", with the first child being the Label. You can also make sure the width matches the default properties by assigning it the correct style class. Use the UIElements Debugger to see where the other property labels get their widths.
     
  29. aybe

    aybe

    Joined:
    Feb 20, 2019
    Posts:
    55
    Alright, thank you.

    Now for the second part, it turns out that no matter how hard you try to replicate the style attached to the label of a PropertyField you can check in the debugger, they never do align properly !
     
  30. crocvr

    crocvr

    Joined:
    Oct 7, 2015
    Posts:
    44
    Hi!
    I am trying to add new CustomPropertyDrawer with UI Elements and I always see this error message in Editor.
    upload_2020-1-29_19-18-19.png

    I use custom attribute for the public field to specify that I want to draw this differently and I cannot use CustomEditor for whole class because it should work for different classes. It worked good with IMGUI but UI Elements is not working properly. So here my code.
    Code (CSharp):
    1. //Attribute
    2. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
    3.     public class WorldStateNameAttributeUIElements : PropertyAttribute
    4.     {
    5.     }
    6.  
    7. //Mono with attribute
    8.   public class WorldStateBehaviourMono : MonoBehaviour
    9.     {
    10.         [WorldStateNameAttributeUIElements] public string worldStateName;
    11.     }
    12.  
    13. //CustomPropertyDrawer
    14. [CustomPropertyDrawer(typeof(WorldStateNameAttributeUIElements), true)]
    15.     public class WorldStateNameDrawerUIElements : PropertyDrawer
    16.     {
    17.         public override VisualElement CreatePropertyGUI(SerializedProperty property)
    18.         {
    19.             var container = new WorldStateNameElement(property);
    20.             return container;
    21.         }
    22.     }
    23.  
    24. //WorldStateNameElement is VisualElement. I tested it with CustomEditor and it is working okay.
    Anyone met this kind of problem? I only found few threads about it that happened in 2014:
    https://answers.unity.com/questions/745266/property-drawer-no-gui-implemented-error-in-editor.html
    https://forum.unity.com/threads/no-gui-implemented.254356/

    Solution from there does not suit because I need to use Visual Element and not IMGUI.
     
  31. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    You will currently need to also implement a custom editor for WorldStateBehaviourMono as the default editor is still using IMGUI.
     
  32. crocvr

    crocvr

    Joined:
    Oct 7, 2015
    Posts:
    44
    The main problem here that if I implement customeditor for my mono scripts I have to manually draw all fields by visual elements because they don’t have default drawers. The case is to draw one specific field with custom drawer and everything else as is in UnityEditor.

    Any ETC on default drawers for UI Elements or changing CustomPropertyDrawer behavior?
     
  33. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    Getting inspiration from @pyrho_luke 's post above, I moved his CreateInspectorGUI method to an helper that you can reuse:

    Code (CSharp):
    1. public static class UIElementsEditorHelper
    2. {
    3.     public static void FillDefaultInspector(VisualElement container, SerializedObject serializedObject, bool hideScript)
    4.     {
    5.         SerializedProperty property = serializedObject.GetIterator();
    6.         if (property.NextVisible(true)) // Expand first child.
    7.         {
    8.             do
    9.             {
    10.                 if (property.propertyPath == "m_Script" && hideScript)
    11.                 {
    12.                     continue;
    13.                 }
    14.                 var field = new PropertyField(property);
    15.                 field.name = "PropertyField:" + property.propertyPath;
    16.  
    17.  
    18.                 if (property.propertyPath == "m_Script" && serializedObject.targetObject != null)
    19.                 {
    20.                     field.SetEnabled(false);
    21.                 }
    22.  
    23.                 container.Add(field);
    24.             }
    25.             while (property.NextVisible(false));
    26.         }
    27.     }
    28. }
    You can then use like so:

    Code (CSharp):
    1. [CustomEditor(typeof(MyMonoBehaviour))]
    2. class DefaultUIElementsEditorWhileWeWaitForTheRealThing : Editor
    3. {
    4.     public override VisualElement CreateInspectorGUI()
    5.     {
    6.         var container = new VisualElement();
    7.         UIElementsEditorHelper.FillDefaultInspector(container, serializedObject, false);
    8.         return container;
    9.     }
    10. }
     
  34. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398

    Sorry for the late reply Starducky. The binding system only binds to the current SerializedObject used by the Inspector. If you create another one for your object reference, you need to explicitly bind it to your sub hierarchy by calling container.Bind(so). I updated your inlined code for easier future copy-paste :)
     
  35. Darksunrise957

    Darksunrise957

    Joined:
    Jul 1, 2015
    Posts:
    15
    Hi. I'm looking for a bit of help understanding the code in here. I've made a custom editor window (Which works fine, no issues there) that's supposed to open when I click a button on a ScriptableObject in the editor, but it seems to replace the whole editor panel with just the button I wrote. Googling helped me find this page.

    If I understand right, as soon as you override OnInspectorGUI, you then have to draw every inspector field in that class yourself? So I followed the code piho_luke posted, and it seems to now draw the panel as it should... Except now my Custom Editor adding the button seems to do nothing. I don't see a button there anymore.

    So I changed it to uMathieu's code, which seems like a very similar thing, just with a couple tweaks?
    It's still drawing only the "default" editor, though. What does the bool "hidescript" do? That seems to be the biggest difference in the code I spotted.

    I could understand more if I had to call VisualElement myself, but it seems to be called automatically by the inspector? At least that's what I'm assuming, or I don't think the script would do anything, seeing as I never call it in a script, so something must be...

    How do I use this code to display my button, and all the default fields in the inspector below it?

    Some help would be really appreciated. Thanks! :)
     
  36. fherbst

    fherbst

    Joined:
    Jun 24, 2012
    Posts:
    802
    Add a method to the code above
    Code (CSharp):
    1.     public override VisualElement CreateInspectorGUI()
    2.     {
    3.         var container = new VisualElement();
    4.         UIElementsEditorHelper.FillDefaultInspector(container, serializedObject, false);
    5.         AddMyCustomUI(container); // this is where your custom stuff comes into play
    6.         return container;
    7.     }
    Your custom UI etc. can go here:
    Code (CSharp):
    1. void AddMyCustomUI(VisualElement container) {
    2.   Button button = new Button();
    3.   b.text = "hello";
    4.   container.Add(b);
    5. }
     
    uDamian likes this.
  37. Araj

    Araj

    Joined:
    Jan 3, 2013
    Posts:
    27
    Hi!

    I'm trying to create a custom array inspector, so far I managed to create a default inspector that works for all UnityEngine.Object (very similar to what @uMathieu posted:

    Code (CSharp):
    1. [CanEditMultipleObjects]
    2.     [CustomEditor(typeof(Object), true, isFallback = true)]
    3.     public class DefaultEditor : Editor
    4.     {
    5.         public override VisualElement CreateInspectorGUI()
    6.         {
    7.             var container = new VisualElement();
    8.  
    9.             var iterator = serializedObject.GetIterator();
    10.             if (iterator.NextVisible(true))
    11.             {
    12.                 do
    13.                 {
    14.                     var propertyField = new PropertyField(iterator.Copy())
    15.                     {
    16.                         name = "PropertyField:" + iterator.propertyPath
    17.                     };
    18.  
    19.                     if (iterator.propertyPath == "m_Script" && serializedObject.targetObject != null)
    20.                     {
    21.                         propertyField.SetEnabled(value: false);
    22.                     }
    23.  
    24.                     if (iterator.name == "testList")
    25.                     {
    26.                         var test = CreateFoldout(iterator);
    27.                         container.Add(test);
    28.                     }
    29.                     else
    30.                     {
    31.                         container.Add(propertyField);
    32.                     }
    33.  
    34.                 } while (iterator.NextVisible(false));
    35.             }
    36.  
    37.             return container;
    38.         }
    39.  
    40.         private VisualElement CreateFoldout(SerializedProperty property)
    41.         {
    42.             property = property.Copy();
    43.             Foldout e = new Foldout();
    44.             e.text = "TEST FOLDOUT";
    45.             e.value = property.isExpanded;
    46.             e.bindingPath = property.propertyPath;
    47.             e.name = "unity-foldout-" + property.propertyPath;
    48.             Label label = e.Q<Toggle>((string) null, Foldout.toggleUssClassName).Q<Label>((string) null, Toggle.textUssClassName);
    49.             label.bindingPath = property.propertyPath;
    50.             SerializedProperty endProperty = property.GetEndProperty();
    51.             property.NextVisible(true);
    52.             while (!SerializedProperty.EqualContents(property, endProperty))
    53.             {
    54.                 PropertyField propertyField = new PropertyField(property);
    55.                 propertyField.name = "unity-property-field-" + property.propertyPath;
    56.                 if (propertyField != null)
    57.                     e.Add((VisualElement) propertyField);
    58.                 if (!property.NextVisible(false))
    59.                     break;
    60.             }
    61.             return (VisualElement) e;
    62.         }
    63. }
    With the CreateFoldout method I managed to create a Foldout for a specific element (iterator.name == "testList"), but I would like to know if there is a way to detect an array or an IList type to be able to create a custom drawer for those types.

    Thank you!
     
  38. Darksunrise957

    Darksunrise957

    Joined:
    Jul 1, 2015
    Posts:
    15
    Ah, perfect! That was what I was missing. I also hooked it up with an override so I can call it from my custom editor, or any others I make in the future. I was wondering if I now understood it right to do that, but it seems to be working thanks to your help! :)
     
    fherbst likes this.
  39. Araj

    Araj

    Joined:
    Jan 3, 2013
    Posts:
    27
    Hi!
    Sorry to resurrect an old post, but I would like to know if an element has a custom PropertyDrawer from code.
     
  40. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Not using any public APIs as far as I know (not 100% on this, could try the IMGUI forum since this is unrelated to UI Toolkit specifically). You could use reflection to search for any types that have the [CustomPropertyDrawer(..)] attribute with your data type. Note that _elements_ don't have PropertyDrawers, it's the data types being edited that can have PropertyDrawers.
     
  41. BlackHoleTracer

    BlackHoleTracer

    Joined:
    Jan 23, 2014
    Posts:
    7
    I look forward to the implementation of the UI Toolkit default inspector with support of reorderable arrays (because IMGUI now support it, but UIElements PropertyField does not support it).
    https://forum.unity.com/threads/tha...-the-inspector-can-be-reorderable-now.904778/
     
  42. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Yep, this is being actively worked on.
     
  43. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Maybe instead of being empty, can PropertyField show warning box "SerializedObject binding is not specified"?
    It's not the first time when I forgot to explicitly call Bind(so) :)
     
  44. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    @uDamian, any ETA on when we can either:
    - create a VisualElement based PropertyDrawer and have it just work without having to also manually build a VisualElement based Editor for everything that wants to use that PropertyDrawer
    or
    - Create a VisualElement based custom Editor that works by default (like the posts upthread attempts to do, but in a built-in way)?

    The convenience of creating PropertyDrawers is based around not having to make custom editors, so without that step they're pretty much useless.
     
    RunninglVlan and Oneiros90 like this.
  45. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    I have a scriptable object with a list of interface. I made a custom editor wiht visual element to edit that list and allow hte user to select among the type implementing the inteface and then display the element and their data based on their actual type. It works well enougth but the editor code is a bit messy. PLus i have another similar list with a different interface.

    I wanted to implement a property drawer to e able to reuse that and also I would have like to be able to have the "new" reoderable list look. Unfortunalty, in addition to the visual element imgui editor issue discussed in this thread, as soon as I declare a custom property drawer for the list element, I lose the reorderable "style".

    You seem to say that there is no property drawer for a List, only for the element of that List.
    Is there any way to keep the reorderable style and define a custom drawer for the list elements ?

    Doing everything with non visual element property drawer could work but to be honest I really don't want to have to go with that ...
     
  46. xotonic

    xotonic

    Joined:
    Feb 21, 2016
    Posts:
    11
    Any ideas why it does not add any height to the inspector?
    UPD: I've just have reread the thread and it looks like PropertyDrawer for UIE is not ready yet.
    What is the remaining approach then? Making Editors for every class?

    upload_2021-7-3_22-59-36.png

    Code (CSharp):
    1.     [Serializable]
    2.     public class BlockNodeInspectorView
    3.     {
    4.         public BlockType BlockType;
    5.         public ChildrenMaskInspectorView ChildrenMask;
    6.     }
    7.     [Serializable]
    8.     public class ChildrenMaskInspectorView
    9.     {
    10.         public bool childFront, childBack, childLeft, childRight, childTop, childBottom;
    11.     }
    12.  
    13.     [CustomEditor(typeof(BlockNodeInspectorView))]
    14.     public class BlockNodeEditor : UnityEditor.Editor
    15.     {
    16.         public override VisualElement CreateInspectorGUI()
    17.         {
    18.             var container = new VisualElement();
    19.             container.Add(new PropertyField(serializedObject.FindProperty("BlockType")));
    20.             container.Add(new PropertyField(serializedObject.FindProperty("ChildrenMask")));
    21.             return container;
    22.         }
    23.     }
    24.  
    25.     [CustomPropertyDrawer(typeof(ChildrenMaskInspectorView))]
    26.     public class ChildrenMaskEditor : PropertyDrawer
    27.     {
    28.         public override VisualElement CreatePropertyGUI(SerializedProperty property)
    29.         {
    30.             var cubeNetTree = AssetDatabase
    31.                 .LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/CubeNetTemplate.uxml")
    32.                 .Instantiate();
    33.             return cubeNetTree;
    34.         }
    35.     }
    36.  
    Code (xml):
    1.  
    2. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
    3.     <ui:VisualElement name="template-root" style="flex-direction: column; flex-wrap: nowrap; flex-basis: 0; align-items: stretch; position: absolute; left: 16px; top: 16px; right: 16px; bottom: 16px; justify-content: center;">
    4.         <ui:VisualElement name="front" tooltip="Front" style="min-width: 32px; min-height: 32px; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 0, 0); align-items: center; justify-content: center; width: 32px; height: 32px; background-color: rgb(36, 36, 36); -unity-text-align: middle-left; position: absolute; left: 32px; top: 32px; max-width: 32px; max-height: 32px;">
    5.             <ui:Toggle value="false" />
    6.         </ui:VisualElement>
    7.         <ui:VisualElement name="top" tooltip="Top" style="min-width: 32px; min-height: 32px; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 0, 0); align-items: center; justify-content: center; width: 32px; height: 32px; background-color: rgb(36, 36, 36); -unity-text-align: middle-left; flex-wrap: nowrap; flex-direction: column; position: absolute; left: 32px; top: 0; max-width: 32px; max-height: 32px;">
    8.             <ui:Toggle value="false" />
    9.         </ui:VisualElement>
    10.         <ui:VisualElement name="bottom" tooltip="Bottpm" style="min-width: 32px; min-height: 32px; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 0, 0); align-items: center; justify-content: center; width: 32px; height: 32px; background-color: rgb(36, 36, 36); -unity-text-align: middle-left; position: absolute; top: 64px; left: 32px; max-width: 32px; max-height: 32px;">
    11.             <ui:Toggle value="false" />
    12.         </ui:VisualElement>
    13.         <ui:VisualElement name="back" tooltip="Back" style="min-width: 32px; min-height: 32px; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 0, 0); align-items: center; justify-content: center; width: 32px; height: 32px; background-color: rgb(36, 36, 36); -unity-text-align: middle-left; position: absolute; top: 64px; left: 0; max-width: 32px; max-height: 32px;">
    14.             <ui:Toggle value="false" />
    15.         </ui:VisualElement>
    16.         <ui:VisualElement name="left" tooltip="Left" style="min-width: 32px; min-height: 32px; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 0, 0); align-items: center; justify-content: center; width: 32px; height: 32px; background-color: rgb(36, 36, 36); -unity-text-align: middle-left; position: absolute; top: 32px; left: 0; max-width: 32px; max-height: 32px;">
    17.             <ui:Toggle value="false" />
    18.         </ui:VisualElement>
    19.         <ui:VisualElement name="right" tooltip="Right" style="min-width: 32px; min-height: 32px; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-left-color: rgb(0, 0, 0); border-right-color: rgb(0, 0, 0); border-top-color: rgb(0, 0, 0); border-bottom-color: rgb(0, 0, 0); align-items: center; justify-content: center; width: 32px; height: 32px; background-color: rgb(36, 36, 36); -unity-text-align: middle-left; position: absolute; top: 32px; left: 64px;">
    20.             <ui:Toggle value="false" />
    21.         </ui:VisualElement>
    22.     </ui:VisualElement>
    23. </ui:UXML>
    24.  
     
    Last edited: Jul 3, 2021
  47. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Visual element property drawer only work with a visual element editor.
    So if your octreedebug does not have a custom visual element editor then that might be the issue.
    But since you have something displayed I don't think that's it.
    Have you tried using the visual element debugger to check the height of your problematic element and see from where it is set ?
     
  48. xotonic

    xotonic

    Joined:
    Feb 21, 2016
    Posts:
    11
    For some reason, the property field with my uxml has the height set to 0 and also this tag "inline"
    The other built-in property fields have height auto and no inline. Other properties seem to be identical.
    Setting the a fixed value inside the debugger helps but I can't set the height for it in the code because the set methods are private

    upload_2021-7-4_15-25-10.png
     

    Attached Files:

  49. xotonic

    xotonic

    Joined:
    Feb 21, 2016
    Posts:
    11
    Found the problem. My root VisualElement in the uxml had
    position: absolute;
    in style. I've changed it do relative and set fixed size and now it works
     
    WAYNGames likes this.
  50. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    168
    How do you reference a VisualTreeAsset in a script for a CustomPropertyDrawer? I can get it to work for a custom inspector by exposing a serialized field of type VisualTreeAsset, and dragging the asset into the default field when clicking on the script in the project view. It doesn't seem to be the same for custom property drawer

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(RandomizableFloat))]
    2. public class RandomizableFloatPropertyDrawer : PropertyDrawer {
    3.  
    4.     [SerializeField] VisualTreeAsset RandomizableFloatDrawer;
    5.  
    6.     public override VisualElement CreatePropertyGUI(SerializedProperty property)
    7.     {
    8.         // Create property container element.
    9.         var container = new VisualElement();
    10.  
    11.         //and so on...
    the above code has no field to drag the reference to.