Search Unity

Resolved Custom UI Builder Attribute Fields

Discussion in 'UI Toolkit' started by Guedez, Aug 4, 2020.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I've looked all over the packages and couldn't find...
    I want to use it as reference for a Type field that lets you pick from a Type's subtypes
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    EnumField is part of core, but it's only available in the Editor (not for Runtime). Where are you looking for it? Which packages?

    Also, for what you describe, you may need to use PopupField instead if you want to control what the options are. EnumField just takes an Enum and derives the options from the options in the Enum type. You cannot add/remove options.
     
  3. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I am attempting to put a type picker here:

    I got so far as making a custom
    UxmlClassAttributeDescription : TypedUxmlAttributeDescription<Type>

    It does use restrictions, but it seems to do nothing.
    Code (CSharp):
    1.         UxmlEnumeration enumRestriction = new UxmlEnumeration();
    2.  
    3.         var values = new List<string>();
    4.         //GetAllClassesExtending exclusing any assemblies containing those strings
    5.         Type[] types = Utils.GetAllClassesExtending<Manipulator>("Unity", "Microsoft", "System", "Mono", "UMotion");
    6.         Array.Sort(types, (T1, T2) => T2.FullName.CompareTo(T1.FullName));
    7.         values = types.Cast(T => T.FullName).ToList();
    8.         enumRestriction.values = values;
    9.  
    10.         restriction = enumRestriction;
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEngine.UIElements;
    5.  
    6. public class UxmlClassAttributeDescription : TypedUxmlAttributeDescription<Type> {
    7.     public UxmlClassAttributeDescription() {
    8.  
    9.         type = "Type";
    10.         typeNamespace = xmlSchemaNamespace;
    11.         defaultValue = typeof(Type);
    12.  
    13.         UxmlEnumeration enumRestriction = new UxmlEnumeration();
    14.  
    15.         var values = new List<string>();
    16.  
    17.         Type[] types = Utils.GetAllClassesExtending<Manipulator>("Unity", "Microsoft", "System", "Mono", "UMotion");
    18.         Array.Sort(types, (T1, T2) => T2.FullName.CompareTo(T1.FullName));
    19.         values = types.Cast(T => T.FullName).ToList();
    20.         enumRestriction.values = values;
    21.  
    22.         restriction = enumRestriction;
    23.     }
    24.  
    25.     public override string defaultValueAsString { get { return defaultValue.AssemblyQualifiedName; } }
    26.  
    27.     public override Type GetValueFromBag(IUxmlAttributes bag, CreationContext cc) {
    28.         return GetValueFromBag(bag, cc, (s, t) => {
    29.             Type tp = Type.GetType(s);
    30.             if (tp != null) {
    31.                 return tp;
    32.             }
    33.             return t;
    34.         }, defaultValue);
    35.     }
    36.     public bool TryGetValueFromBag(IUxmlAttributes bag, CreationContext cc, ref Type value) {
    37.         return TryGetValueFromBag(bag, cc, (s, t) => {
    38.             Type tp = Type.GetType(s);
    39.             if (tp != null) {
    40.                 return tp;
    41.             }
    42.             return t;
    43.         }, defaultValue, ref value);
    44.     }
    45. }
     
  4. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    As a bonus question, can I make extra fields to appear depending on the value of the Manipulator field?
    Then I would be able to put per-manipulator parameters
     
  5. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Ah, you're tying to customize the UI Builder's Attributes Inspector. This part of the Builder is fairly limited as the focus until now has mostly been to support the default set of UXML attribute types. For example, it does not take into account enumRestriction. It's something we'll address in future releases of the Builder but it's still not our highest priority.

    Now, if I were in your position, without the ability to change the code of the Builder, what I'd do is abuse the fact that the UI Builder is just as UI Toolkit as you are. What I mean is, in your custom AddManipulator element, when you get the AttachToPanel event, you can walk up your parents and determine if you are inside the UI Builder's Canvas. Not only that, but you can then walk back down and find the Builder's Attributes Inspector and start adding your own custom fields in there. You can use the UI Debugger to get a sense of the Builder's element hierarchy and what element names to Query for.

    Of course, you'll be on your own and you'll be responsible for cleaning up any fields you add to the Inspector on de-selection and/or removal of your custom element. But it's..possible.
     
  6. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Now that's some crazy stuff, good to know that it's that unconventionally extensible, but i'd rather wait for a conventional way to manipulate that and just use a monobehaviour to attach manipulators until then.
    Unless extending the UI Builder's Attributes Inspector is scheduled for 2022+, then I guess it will be worth going through the effort to "hack" my way in as you proposed.
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    After a bit of testing, I figured out that extending the builder will actually be much faster than making the monobehaviours to add the manipulators. So I have to leave a request after reading some of the sources.

    Create a "
    ICustomAttributeField
    " interface that have a "
    BindableElement CreateField()
    " method to it and place a hook for it in
    BuilderInspectorAttributes.cs
    right before the last
    } else {
    of
    BuilderStyleRow CreateAttributeRow(UxmlAttributeDescription attribute)
    (line 167 as of my version) that checks if the attribute have the interface, and invokes the interface method to build the field.
    As I understand from the source, that should instantly give us access to custom attribute fields without all of this workaround.
    I mean, an 'unknown' Uxml***AttributeDescription will default to just 3 simple lines:
    Code (CSharp):
    1.                 var uiField = new TextField(fieldLabel);
    2.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    3.                 fieldElement = uiField;
    Meanwhile I will fumble about and attempt to do the workaround.

    I actually did manage to implement the feature myself:

    Of course, since it changed
    BuilderInspectorAttributes.cs
    , it will keep being undone constantly. Hopefully there are no issues with simply incorporating this change into the code, or a similar better way to go about it.
    Source:
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Reflection;
    4. using System.Text.RegularExpressions;
    5. using UnityEditor;
    6. using UnityEditor.UIElements;
    7. using UnityEngine;
    8. using UnityEngine.UIElements;
    9. public interface ICustomAttributeField {
    10.     BindableElement BuildField(string fieldLabel, EventCallback<ChangeEvent<string>> bind);
    11. }
    12. namespace Unity.UI.Builder
    13. {
    14.     internal class BuilderInspectorAttributes : IBuilderInspectorSection
    15.     {
    16.         BuilderInspector m_Inspector;
    17.         BuilderSelection m_Selection;
    18.         PersistedFoldout m_AttributesSection;
    19.  
    20.         VisualElement currentVisualElement => m_Inspector.currentVisualElement;
    21.  
    22.         public VisualElement root => m_AttributesSection;
    23.  
    24.         public BuilderInspectorAttributes(BuilderInspector inspector)
    25.         {
    26.             m_Inspector = inspector;
    27.             m_Selection = inspector.selection;
    28.  
    29.             m_AttributesSection = m_Inspector.Q<PersistedFoldout>("inspector-attributes-foldout");
    30.         }
    31.  
    32.         public void Refresh()
    33.         {
    34.             m_AttributesSection.Clear();
    35.  
    36.             if (currentVisualElement == null)
    37.                 return;
    38.  
    39.             m_AttributesSection.text = currentVisualElement.typeName;
    40.  
    41.             if (m_Selection.selectionType != BuilderSelectionType.Element &&
    42.                 m_Selection.selectionType != BuilderSelectionType.ElementInTemplateInstance)
    43.                 return;
    44.  
    45.             GenerateAttributeFields();
    46.  
    47.             // Forward focus to the panel header.
    48.             m_AttributesSection
    49.                 .Query()
    50.                 .Where(e => e.focusable)
    51.                 .ForEach((e) => m_Inspector.AddFocusable(e));
    52.         }
    53.  
    54.         public void Enable()
    55.         {
    56.             m_AttributesSection.contentContainer.SetEnabled(true);
    57.         }
    58.  
    59.         public void Disable()
    60.         {
    61.             m_AttributesSection.contentContainer.SetEnabled(false);
    62.         }
    63.  
    64.         void GenerateAttributeFields()
    65.         {
    66.             var attributeList = currentVisualElement.GetAttributeDescriptions();
    67.  
    68.             foreach (var attribute in attributeList)
    69.             {
    70.                 if (attribute == null || attribute.name == null)
    71.                     continue;
    72.  
    73.                 var styleRow = CreateAttributeRow(attribute);
    74.                 m_AttributesSection.Add(styleRow);
    75.             }
    76.         }
    77.  
    78.         BuilderStyleRow CreateAttributeRow(UxmlAttributeDescription attribute)
    79.         {
    80.             var attributeType = attribute.GetType();
    81.  
    82.             // Generate field label.
    83.             var fieldLabel = BuilderNameUtilities.ConvertDashToHuman(attribute.name);
    84.             BindableElement fieldElement;
    85.             if (attribute is UxmlStringAttributeDescription)
    86.             {
    87.                 var uiField = new TextField(fieldLabel);
    88.                 if (attribute.name.Equals("name") || attribute.name.Equals("view-data-key"))
    89.                     uiField.RegisterValueChangedCallback(e =>
    90.                     {
    91.                         OnValidatedAttributeValueChange(e, BuilderNameUtilities.AttributeRegex, BuilderConstants.AttributeValidationSpacialCharacters);
    92.                     });
    93.                 else if (attribute.name.Equals("binding-path"))
    94.                     uiField.RegisterValueChangedCallback(e =>
    95.                     {
    96.                         OnValidatedAttributeValueChange(e, BuilderNameUtilities.BindingPathAttributeRegex, BuilderConstants.BindingPathAttributeValidationSpacialCharacters);
    97.                     });
    98.                 else
    99.                     uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    100.  
    101.                 if (attribute.name.Equals("text"))
    102.                 {
    103.                     uiField.multiline = true;
    104.                     uiField.AddToClassList(BuilderConstants.InspectorMultiLineTextFieldClassName);
    105.                 }
    106.  
    107.                 fieldElement = uiField;
    108.             }
    109.             else if (attribute is UxmlFloatAttributeDescription)
    110.             {
    111.                 var uiField = new FloatField(fieldLabel);
    112.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    113.                 fieldElement = uiField;
    114.             }
    115.             else if (attribute is UxmlDoubleAttributeDescription)
    116.             {
    117.                 var uiField = new DoubleField(fieldLabel);
    118.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    119.                 fieldElement = uiField;
    120.             }
    121.             else if (attribute is UxmlIntAttributeDescription)
    122.             {
    123.                 var uiField = new IntegerField(fieldLabel);
    124.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    125.                 fieldElement = uiField;
    126.             }
    127.             else if (attribute is UxmlLongAttributeDescription)
    128.             {
    129.                 var uiField = new LongField(fieldLabel);
    130.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    131.                 fieldElement = uiField;
    132.             }
    133.             else if (attribute is UxmlBoolAttributeDescription)
    134.             {
    135.                 var uiField = new Toggle(fieldLabel);
    136.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    137.                 fieldElement = uiField;
    138.             }
    139.             else if (attribute is UxmlColorAttributeDescription)
    140.             {
    141.                 var uiField = new ColorField(fieldLabel);
    142.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    143.                 fieldElement = uiField;
    144.             }
    145.             else if (attributeType.IsGenericType &&
    146.                 !attributeType.GetGenericArguments()[0].IsEnum &&
    147.                 attributeType.GetGenericArguments()[0] is Type)
    148.             {
    149.                 var uiField = new TextField(fieldLabel);
    150.                 uiField.isDelayed = true;
    151.                 uiField.RegisterValueChangedCallback(e =>
    152.                 {
    153.                     OnValidatedTypeAttributeChange(e, attributeType.GetGenericArguments()[0]);
    154.                 });
    155.                 fieldElement = uiField;
    156.             }
    157.             else if (attributeType.IsGenericType && attributeType.GetGenericArguments()[0].IsEnum)
    158.             {
    159.                 var propInfo = attributeType.GetProperty("defaultValue");
    160.                 var enumValue = propInfo.GetValue(attribute, null) as Enum;
    161.  
    162.                 // Create and initialize the EnumField.
    163.                 var uiField = new EnumField(fieldLabel);
    164.                 uiField.Init(enumValue);
    165.  
    166.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    167.                 fieldElement = uiField;
    168.             }
    169.             else if (typeof(ICustomAttributeField).IsAssignableFrom(attribute.GetType())) {
    170.                 fieldElement = ((ICustomAttributeField)attribute).BuildField(fieldLabel, OnAttributeValueChange);
    171.             }
    172.             else
    173.             {
    174.                 var uiField = new TextField(fieldLabel);
    175.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    176.                 fieldElement = uiField;
    177.             }
    178.  
    179.             // Create row.
    180.             var styleRow = new BuilderStyleRow();
    181.             styleRow.Add(fieldElement);
    182.  
    183.             // Link the field.
    184.             fieldElement.SetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName, styleRow);
    185.             fieldElement.SetProperty(BuilderConstants.InspectorLinkedAttributeDescriptionVEPropertyName, attribute);
    186.  
    187.             // Set initial value.
    188.             RefreshAttributeField(fieldElement);
    189.  
    190.             // Setup field binding path.
    191.             fieldElement.bindingPath = attribute.name;
    192.  
    193.             // Tooltip.
    194.             var label = fieldElement.Q<Label>();
    195.             if (label != null)
    196.                 label.tooltip = attribute.name;
    197.             else
    198.                 fieldElement.tooltip = attribute.name;
    199.  
    200.             // Context menu.
    201.             fieldElement.AddManipulator(new ContextualMenuManipulator(BuildAttributeFieldContextualMenu));
    202.  
    203.             return styleRow;
    204.         }
    205.  
    206.         object GetCustomValueAbstract(string attributeName)
    207.         {
    208.             if (currentVisualElement is ScrollView)
    209.             {
    210.                 var scrollView = currentVisualElement as ScrollView;
    211.                 if (attributeName == "mode")
    212.                 {
    213.                     if (scrollView.ClassListContains(ScrollView.verticalVariantUssClassName))
    214.                         return ScrollViewMode.Vertical;
    215.                     else if (scrollView.ClassListContains(ScrollView.horizontalVariantUssClassName))
    216.                         return ScrollViewMode.Horizontal;
    217.                     else if (scrollView.ClassListContains(ScrollView.verticalHorizontalVariantUssClassName))
    218.                         return ScrollViewMode.VerticalAndHorizontal;
    219.                 }
    220.                 else if (attributeName == "show-horizontal-scroller")
    221.                 {
    222.                     return scrollView.showHorizontal;
    223.                 }
    224.                 else if (attributeName == "show-vertical-scroller")
    225.                 {
    226.                     return scrollView.showVertical;
    227.                 }
    228.             }
    229.             else if (currentVisualElement is ObjectField objectField)
    230.             {
    231.                 if (attributeName == "type")
    232.                 {
    233.                     return objectField.objectType;
    234.                 }
    235.             }
    236.  
    237.             return null;
    238.         }
    239.  
    240.         void RefreshAttributeField(BindableElement fieldElement)
    241.         {
    242.             var styleRow = fieldElement.GetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName) as VisualElement;
    243.             var attribute = fieldElement.GetProperty(BuilderConstants.InspectorLinkedAttributeDescriptionVEPropertyName) as UxmlAttributeDescription;
    244.  
    245.             var veType = currentVisualElement.GetType();
    246.             var camel = BuilderNameUtilities.ConvertDashToCamel(attribute.name);
    247.  
    248.             var fieldInfo = veType.GetProperty(camel, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
    249.  
    250.             object veValueAbstract = null;
    251.             if (fieldInfo == null)
    252.             {
    253.                 veValueAbstract = GetCustomValueAbstract(attribute.name);
    254.             }
    255.             else
    256.             {
    257.                 veValueAbstract = fieldInfo.GetValue(currentVisualElement);
    258.             }
    259.             if (veValueAbstract == null)
    260.                 return;
    261.  
    262.             var attributeType = attribute.GetType();
    263.             var vea = currentVisualElement.GetVisualElementAsset();
    264.  
    265.             if (attribute is UxmlStringAttributeDescription && fieldElement is TextField)
    266.             {
    267.                 (fieldElement as TextField).SetValueWithoutNotify(GetAttributeStringValue(veValueAbstract));
    268.             }
    269.             else if (attribute is UxmlFloatAttributeDescription && fieldElement is FloatField)
    270.             {
    271.                 (fieldElement as FloatField).SetValueWithoutNotify((float)veValueAbstract);
    272.             }
    273.             else if (attribute is UxmlDoubleAttributeDescription && fieldElement is DoubleField)
    274.             {
    275.                 (fieldElement as DoubleField).SetValueWithoutNotify((double)veValueAbstract);
    276.             }
    277.             else if (attribute is UxmlIntAttributeDescription && fieldElement is IntegerField)
    278.             {
    279.                 if (veValueAbstract is int)
    280.                     (fieldElement as IntegerField).SetValueWithoutNotify((int)veValueAbstract);
    281.                 else if (veValueAbstract is float)
    282.                     (fieldElement as IntegerField).SetValueWithoutNotify(Convert.ToInt32(veValueAbstract));
    283.             }
    284.             else if (attribute is UxmlLongAttributeDescription && fieldElement is LongField)
    285.             {
    286.                 (fieldElement as LongField).SetValueWithoutNotify((long)veValueAbstract);
    287.             }
    288.             else if (attribute is UxmlBoolAttributeDescription && fieldElement is Toggle)
    289.             {
    290.                 (fieldElement as Toggle).SetValueWithoutNotify((bool)veValueAbstract);
    291.             }
    292.             else if (attribute is UxmlColorAttributeDescription && fieldElement is ColorField)
    293.             {
    294.                 (fieldElement as ColorField).SetValueWithoutNotify((Color)veValueAbstract);
    295.             }
    296.             else if (attributeType.IsGenericType &&
    297.                 !attributeType.GetGenericArguments()[0].IsEnum &&
    298.                 attributeType.GetGenericArguments()[0] is Type &&
    299.                 fieldElement is TextField textField &&
    300.                 veValueAbstract is Type veTypeValue)
    301.             {
    302.                 var fullTypeName = veTypeValue.AssemblyQualifiedName;
    303.                 var fullTypeNameSplit = fullTypeName.Split(',');
    304.                 textField.SetValueWithoutNotify($"{fullTypeNameSplit[0]},{fullTypeNameSplit[1]}");
    305.             }
    306.             else if (attributeType.IsGenericType &&
    307.                 attributeType.GetGenericArguments()[0].IsEnum &&
    308.                 fieldElement is EnumField)
    309.             {
    310.                 var propInfo = attributeType.GetProperty("defaultValue");
    311.                 var enumValue = propInfo.GetValue(attribute, null) as Enum;
    312.  
    313.                 // Create and initialize the EnumField.
    314.                 var uiField = fieldElement as EnumField;
    315.  
    316.                 // Set the value from the UXML attribute.
    317.                 var enumAttributeValueStr = vea?.GetAttributeValue(attribute.name);
    318.                 if (!string.IsNullOrEmpty(enumAttributeValueStr))
    319.                 {
    320.                     var parsedValue = Enum.Parse(enumValue.GetType(), enumAttributeValueStr, true) as Enum;
    321.                     uiField.SetValueWithoutNotify(parsedValue);
    322.                 }
    323.             }
    324.             else if (fieldElement is TextField)
    325.             {
    326.                 (fieldElement as TextField).SetValueWithoutNotify(veValueAbstract.ToString());
    327.             }
    328.  
    329.             styleRow.RemoveFromClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    330.             if (IsAttributeOverriden(attribute))
    331.                 styleRow.AddToClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    332.         }
    333.  
    334.         string GetAttributeStringValue(object attributeValue)
    335.         {
    336.             string value;
    337.             if (attributeValue is Enum @enum)
    338.                 value = @enum.ToString();
    339.             else if (attributeValue is IList<string> list)
    340.             {
    341.                 value = string.Join(",", list);
    342.             }
    343.             else
    344.             {
    345.                 value = attributeValue.ToString();
    346.             }
    347.  
    348.             return value;
    349.         }
    350.  
    351.         bool IsAttributeOverriden(UxmlAttributeDescription attribute)
    352.         {
    353.             var vea = currentVisualElement.GetVisualElementAsset();
    354.             if (vea != null && attribute.name == "picking-mode")
    355.             {
    356.                 var veaAttributeValue = vea.GetAttributeValue(attribute.name);
    357.                 if (veaAttributeValue != null &&
    358.                     veaAttributeValue.ToLower() != attribute.defaultValueAsString.ToLower())
    359.                     return true;
    360.             }
    361.             else if (attribute.name == "name")
    362.             {
    363.                 if (!string.IsNullOrEmpty(currentVisualElement.name))
    364.                     return true;
    365.             }
    366.             else if (vea != null && vea.HasAttribute(attribute.name))
    367.                 return true;
    368.  
    369.             return false;
    370.         }
    371.  
    372.         void ResetAttributeFieldToDefault(BindableElement fieldElement)
    373.         {
    374.             var styleRow = fieldElement.GetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName) as VisualElement;
    375.             var attribute = fieldElement.GetProperty(BuilderConstants.InspectorLinkedAttributeDescriptionVEPropertyName) as UxmlAttributeDescription;
    376.  
    377.             var attributeType = attribute.GetType();
    378.             var vea = currentVisualElement.GetVisualElementAsset();
    379.  
    380.             if (attribute is UxmlStringAttributeDescription && fieldElement is TextField)
    381.             {
    382.                 var a = attribute as UxmlStringAttributeDescription;
    383.                 var f = fieldElement as TextField;
    384.                 f.SetValueWithoutNotify(a.defaultValue);
    385.             }
    386.             else if (attribute is UxmlFloatAttributeDescription && fieldElement is FloatField)
    387.             {
    388.                 var a = attribute as UxmlFloatAttributeDescription;
    389.                 var f = fieldElement as FloatField;
    390.                 f.SetValueWithoutNotify(a.defaultValue);
    391.             }
    392.             else if (attribute is UxmlDoubleAttributeDescription && fieldElement is DoubleField)
    393.             {
    394.                 var a = attribute as UxmlDoubleAttributeDescription;
    395.                 var f = fieldElement as DoubleField;
    396.                 f.SetValueWithoutNotify(a.defaultValue);
    397.             }
    398.             else if (attribute is UxmlIntAttributeDescription && fieldElement is IntegerField)
    399.             {
    400.                 var a = attribute as UxmlIntAttributeDescription;
    401.                 var f = fieldElement as IntegerField;
    402.                 f.SetValueWithoutNotify(a.defaultValue);
    403.             }
    404.             else if (attribute is UxmlLongAttributeDescription && fieldElement is LongField)
    405.             {
    406.                 var a = attribute as UxmlLongAttributeDescription;
    407.                 var f = fieldElement as LongField;
    408.                 f.SetValueWithoutNotify(a.defaultValue);
    409.             }
    410.             else if (attribute is UxmlBoolAttributeDescription && fieldElement is Toggle)
    411.             {
    412.                 var a = attribute as UxmlBoolAttributeDescription;
    413.                 var f = fieldElement as Toggle;
    414.                 f.SetValueWithoutNotify(a.defaultValue);
    415.             }
    416.             else if (attribute is UxmlColorAttributeDescription && fieldElement is ColorField)
    417.             {
    418.                 var a = attribute as UxmlColorAttributeDescription;
    419.                 var f = fieldElement as ColorField;
    420.                 f.SetValueWithoutNotify(a.defaultValue);
    421.             }
    422.             else if (attributeType.IsGenericType &&
    423.                 !attributeType.GetGenericArguments()[0].IsEnum &&
    424.                 attributeType.GetGenericArguments()[0] is Type &&
    425.                 fieldElement is TextField)
    426.             {
    427.                 var a = attribute as TypedUxmlAttributeDescription<Type>;
    428.                 var f = fieldElement as TextField;
    429.                 if (a.defaultValue == null)
    430.                     f.SetValueWithoutNotify(string.Empty);
    431.                 else
    432.                     f.SetValueWithoutNotify(a.defaultValue.ToString());
    433.             }
    434.             else if (attributeType.IsGenericType &&
    435.                 attributeType.GetGenericArguments()[0].IsEnum &&
    436.                 fieldElement is EnumField)
    437.             {
    438.                 var propInfo = attributeType.GetProperty("defaultValue");
    439.                 var enumValue = propInfo.GetValue(attribute, null) as Enum;
    440.  
    441.                 var uiField = fieldElement as EnumField;
    442.                 uiField.SetValueWithoutNotify(enumValue);
    443.             }
    444.             else if (fieldElement is TextField)
    445.             {
    446.                 (fieldElement as TextField).SetValueWithoutNotify(string.Empty);
    447.             }
    448.  
    449.             // Clear override.
    450.             styleRow.RemoveFromClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    451.             var styleFields = styleRow.Query<BindableElement>().ToList();
    452.             foreach (var styleField in styleFields)
    453.             {
    454.                 styleField.RemoveFromClassList(BuilderConstants.InspectorLocalStyleResetClassName);
    455.                 styleField.RemoveFromClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    456.             }
    457.         }
    458.  
    459.         void BuildAttributeFieldContextualMenu(ContextualMenuPopulateEvent evt)
    460.         {
    461.             evt.menu.AppendAction(
    462.                 BuilderConstants.ContextMenuUnsetMessage,
    463.                 UnsetAttributeProperty,
    464.                 action =>
    465.                 {
    466.                     var fieldElement = action.userData as BindableElement;
    467.                     if (fieldElement == null)
    468.                         return DropdownMenuAction.Status.Disabled;
    469.  
    470.                     var attributeName = fieldElement.bindingPath;
    471.                     var vea = currentVisualElement.GetVisualElementAsset();
    472.                     return vea.HasAttribute(attributeName)
    473.                         ? DropdownMenuAction.Status.Normal
    474.                         : DropdownMenuAction.Status.Disabled;
    475.                 },
    476.                 evt.target);
    477.  
    478.             evt.menu.AppendAction(
    479.                 BuilderConstants.ContextMenuUnsetAllMessage,
    480.                 UnsetAllAttributes,
    481.                 action =>
    482.                 {
    483.                     var attributeList = currentVisualElement.GetAttributeDescriptions();
    484.                     foreach (var attribute in attributeList)
    485.                     {
    486.                         if (attribute?.name == null)
    487.                             continue;
    488.  
    489.                         if(IsAttributeOverriden(attribute))
    490.                             return DropdownMenuAction.Status.Normal;
    491.                     }
    492.  
    493.                     return DropdownMenuAction.Status.Disabled;
    494.                 },
    495.                 evt.target);
    496.         }
    497.  
    498.         void UnsetAllAttributes(DropdownMenuAction action)
    499.         {
    500.             var attributeList = currentVisualElement.GetAttributeDescriptions();
    501.  
    502.             // Undo/Redo
    503.             Undo.RegisterCompleteObjectUndo(m_Inspector.visualTreeAsset, BuilderConstants.ChangeAttributeValueUndoMessage);
    504.  
    505.             foreach (var attribute in attributeList)
    506.             {
    507.                 if (attribute?.name == null)
    508.                     continue;
    509.  
    510.                 // Unset value in asset.
    511.                 var vea = currentVisualElement.GetVisualElementAsset();
    512.                 vea.RemoveAttribute(attribute.name);
    513.             }
    514.  
    515.             var fields = m_AttributesSection.Query<BindableElement>().Where(e => !string.IsNullOrEmpty(e.bindingPath)).ToList();
    516.             foreach (var fieldElement in fields)
    517.             {
    518.                 // Reset UI value.
    519.                 ResetAttributeFieldToDefault(fieldElement);
    520.             }
    521.  
    522.             // Call Init();
    523.             CallInitOnElement();
    524.  
    525.             // Notify of changes.
    526.             m_Selection.NotifyOfHierarchyChange(m_Inspector);
    527.         }
    528.  
    529.         void UnsetAttributeProperty(DropdownMenuAction action)
    530.         {
    531.             var fieldElement = action.userData as BindableElement;
    532.             var attributeName = fieldElement.bindingPath;
    533.  
    534.  
    535.             // Undo/Redo
    536.             Undo.RegisterCompleteObjectUndo(m_Inspector.visualTreeAsset, BuilderConstants.ChangeAttributeValueUndoMessage);
    537.  
    538.             // Unset value in asset.
    539.             var vea = currentVisualElement.GetVisualElementAsset();
    540.             vea.RemoveAttribute(attributeName);
    541.  
    542.             // Reset UI value.
    543.             ResetAttributeFieldToDefault(fieldElement);
    544.  
    545.             // Call Init();
    546.             CallInitOnElement();
    547.  
    548.             // Notify of changes.
    549.             m_Selection.NotifyOfHierarchyChange(m_Inspector);
    550.         }
    551.  
    552.         void OnAttributeValueChange(ChangeEvent<string> evt)
    553.         {
    554.             var field = evt.target as TextField;
    555.             PostAttributeValueChange(field, evt.newValue);
    556.         }
    557.  
    558.         void OnValidatedTypeAttributeChange(ChangeEvent<string> evt, Type desiredType)
    559.         {
    560.             var field = evt.target as TextField;
    561.             var typeName = evt.newValue;
    562.             var fullTypeName = typeName;
    563.             if (!string.IsNullOrEmpty(typeName))
    564.             {
    565.                 var type = Type.GetType(fullTypeName, false);
    566.  
    567.                 // Try some auto-fixes.
    568.                 if (type == null)
    569.                 {
    570.                     fullTypeName = typeName + ", UnityEngine.CoreModule";
    571.                     type = Type.GetType(fullTypeName, false);
    572.                 }
    573.                 if (type == null)
    574.                 {
    575.                     fullTypeName = typeName + ", UnityEditor";
    576.                     type = Type.GetType(fullTypeName, false);
    577.                 }
    578.                 if (type == null && typeName.Contains("."))
    579.                 {
    580.                     var split = typeName.Split('.');
    581.                     fullTypeName = typeName + $", {split[0]}.{split[1]}Module";
    582.                     type = Type.GetType(fullTypeName, false);
    583.                 }
    584.  
    585.                 if (type == null)
    586.                 {
    587.                     Builder.ShowWarning(string.Format(BuilderConstants.TypeAttributeInvalidTypeMessage, field.label));
    588.                     evt.StopPropagation();
    589.                     return;
    590.                 }
    591.                 else if (!desiredType.IsAssignableFrom(type))
    592.                 {
    593.                     Builder.ShowWarning(string.Format(BuilderConstants.TypeAttributeMustDeriveFromMessage, field.label, desiredType.FullName));
    594.                     evt.StopPropagation();
    595.                     return;
    596.                 }
    597.             }
    598.  
    599.             field.SetValueWithoutNotify(fullTypeName);
    600.             PostAttributeValueChange(field, fullTypeName);
    601.         }
    602.  
    603.         void OnValidatedAttributeValueChange(ChangeEvent<string> evt, Regex regex, string message)
    604.         {
    605.             var field = evt.target as TextField;
    606.             if (!string.IsNullOrEmpty(evt.newValue) && !regex.IsMatch(evt.newValue))
    607.             {
    608.                 Builder.ShowWarning(string.Format(message, field.label));
    609.                 field.SetValueWithoutNotify(evt.previousValue);
    610.                 evt.StopPropagation();
    611.                 return;
    612.             }
    613.  
    614.             OnAttributeValueChange(evt);
    615.         }
    616.  
    617.         void OnAttributeValueChange(ChangeEvent<float> evt)
    618.         {
    619.             var field = evt.target as FloatField;
    620.             PostAttributeValueChange(field, evt.newValue.ToString());
    621.         }
    622.  
    623.         void OnAttributeValueChange(ChangeEvent<double> evt)
    624.         {
    625.             var field = evt.target as DoubleField;
    626.             PostAttributeValueChange(field, evt.newValue.ToString());
    627.         }
    628.  
    629.         void OnAttributeValueChange(ChangeEvent<int> evt)
    630.         {
    631.             var field = evt.target as IntegerField;
    632.             PostAttributeValueChange(field, evt.newValue.ToString());
    633.         }
    634.  
    635.         void OnAttributeValueChange(ChangeEvent<long> evt)
    636.         {
    637.             var field = evt.target as LongField;
    638.             PostAttributeValueChange(field, evt.newValue.ToString());
    639.         }
    640.  
    641.         void OnAttributeValueChange(ChangeEvent<bool> evt)
    642.         {
    643.             var field = evt.target as Toggle;
    644.             PostAttributeValueChange(field, evt.newValue.ToString().ToLower());
    645.         }
    646.  
    647.         void OnAttributeValueChange(ChangeEvent<Color> evt)
    648.         {
    649.             var field = evt.target as ColorField;
    650.             PostAttributeValueChange(field, "#" + ColorUtility.ToHtmlStringRGBA(evt.newValue));
    651.         }
    652.  
    653.         void OnAttributeValueChange(ChangeEvent<Enum> evt)
    654.         {
    655.             var field = evt.target as EnumField;
    656.             PostAttributeValueChange(field, evt.newValue.ToString());
    657.         }
    658.  
    659.         void PostAttributeValueChange(BindableElement field, string value)
    660.         {
    661.             // Undo/Redo
    662.             Undo.RegisterCompleteObjectUndo(m_Inspector.visualTreeAsset, BuilderConstants.ChangeAttributeValueUndoMessage);
    663.  
    664.             // Set value in asset.
    665.             var vea = currentVisualElement.GetVisualElementAsset();
    666.             vea.SetAttributeValue(field.bindingPath, value);
    667.  
    668.             // Mark field as overridden.
    669.             var styleRow = field.GetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName) as BuilderStyleRow;
    670.             styleRow.AddToClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    671.  
    672.             var styleFields = styleRow.Query<BindableElement>().ToList();
    673.  
    674.             foreach (var styleField in styleFields)
    675.             {
    676.                 styleField.RemoveFromClassList(BuilderConstants.InspectorLocalStyleResetClassName);
    677.                 if (field.bindingPath == styleField.bindingPath)
    678.                 {
    679.                     styleField.AddToClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    680.                 }
    681.                 else if (!string.IsNullOrEmpty(styleField.bindingPath) &&
    682.                     field.bindingPath != styleField.bindingPath &&
    683.                     !styleField.ClassListContains(BuilderConstants.InspectorLocalStyleOverrideClassName))
    684.                 {
    685.                     styleField.AddToClassList(BuilderConstants.InspectorLocalStyleResetClassName);
    686.                 }
    687.             }
    688.  
    689.             // Call Init();
    690.             CallInitOnElement();
    691.  
    692.             // Notify of changes.
    693.             m_Selection.NotifyOfHierarchyChange(m_Inspector);
    694.         }
    695.  
    696.         void CallInitOnElement()
    697.         {
    698.             var fullTypeName = currentVisualElement.GetType().ToString();
    699.  
    700.             if (VisualElementFactoryRegistry.TryGetValue(fullTypeName, out var factoryList))
    701.             {
    702.                 var traits = factoryList[0].GetTraits();
    703.  
    704.                 if (traits == null)
    705.                     return;
    706.  
    707.                 var context = new CreationContext();
    708.                 var vea = currentVisualElement.GetVisualElementAsset();
    709.  
    710.                 try
    711.                 {
    712.                     traits.Init(currentVisualElement, vea, context);
    713.                 }
    714.                 catch
    715.                 {
    716.                     // HACK: This throws in 2019.3.0a4 because usageHints property throws when set after the element has already been added to the panel.
    717.                 }
    718.             }
    719.         }
    720.     }
    721. }
    722.  
    Example usage:
    Code (CSharp):
    1.     public BindableElement BuildField(string fieldLabel, EventCallback<ChangeEvent<string>> bind) {
    2.         var values = new List<string>();
    3.  
    4.         Type[] types = Utils.GetAllClassesExtending<Manipulator>("Unity", "Microsoft", "System", "Mono", "UMotion");
    5.         Array.Sort(types, (T1, T2) => T2.FullName.CompareTo(T1.FullName));
    6.         values = types.Cast(T => T.FullName).ToList();
    7.  
    8.         PopupField<string> textField = new PopupField<string>(fieldLabel, values, values[0]);
    9.  
    10.         textField.RegisterValueChangedCallback(bind);
    11.         return textField;
    12.     }


    Update:

    Code (CSharp):
    1.         void OnAttributeValueChange(ChangeEvent<string> evt)
    2.         {
    3.             var field = evt.target as TextField;
    4.             PostAttributeValueChange(field, evt.newValue);
    5.         }
    reliance on "as TextField" causes a null pointer. I will keep messing with this tomorrow. So it's not a complete solution as of now
     
    Last edited: Aug 7, 2020
  8. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Nice work! Maybe you should apply to one of the open positions on the UI Builder team. :)

    This is definitely not a 2022+ feature. It's a fairly low hanging fruit so once our main goals are done for the 2021.1 release, this should fit fairly easily.

    That said, I'm trying to delay the introduction of a public API for the UI Builder as long as possible. Public APIs tend to be sticky and static for a long time. My thinking on this was feature was to use the existing custom PropertyDrawer framework for extending the UI Builder Inspector for custom UXML attributes. It would work exactly like normal PropertyFields where you just define a new implementation for your specific attribute type. Just needs to bubble up the backlog at this point.
     
  9. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    You mean this one? https://careers.unity.com/position/developer-ui-tools-d-veloppeur/2150545
    Although I have no near future plans on applying for it, I will keep it in mind.
    For now I am much more comfortable simply making what I need and submitting as a contribution, that way I can hopefully get what I need earlier and if it helps the community, all the better.
    As I have been looking around, a
    PropertyDrawer
    alone is non sufficient, there must be some place where the property value is converted to and from string, since the value needs to be written in UXML and read from it back. It could be generalized for the currently implemented value type fields, but serializing an Type or asset reference can't be automated and need a custom serialize/deserialize function. For instance, changing
    var field = evt.target as TextField -> BindableElement;
    did fix the null pointer, but the UXML is getting the Type name rather than the AssemblyQualifiedName (which I assume is the only sure fire way to serialize a Type back and forth to string), also, the field itself might say "MyCustomManipulator" but the UxmlClassAttributeDescription value is probably null, as it have not a single place where the string was converted back to Type.



    Whenever this actually gets integrated or not is but a convenience for me, but this time a working solution:
    OBS:
    RefreshAttributeField
    was not used at all, going against the norm.
    OBS: There are some allocations I wish I didn't need to, but I couldn't figure out how to avoid it while keeping the methods that need implementing minimal
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Reflection;
    4. using System.Text.RegularExpressions;
    5. using UnityEditor;
    6. using UnityEditor.UIElements;
    7. using UnityEngine;
    8. using UnityEngine.UIElements;
    9. namespace Unity.UI.Builder {
    10.     public abstract class CustomUxmlAttributeDescription<T> : TypedUxmlAttributeDescription<T>, CustomUxmlAttributeDescriptionNonGenericMethods {
    11.         //
    12.         private class BindableElementContainer {
    13.             public BindableElement element;
    14.         }
    15.         public BindableElement BuildField(IUxmlAttributes bag, CreationContext cc, string fieldLabel, EventCallback<BindableElement, string> bind) {
    16.             T CurrentValue = GetValueFromBag(bag, cc);
    17.             BindableElementContainer container = new BindableElementContainer();
    18.             EventCallback<T> callback = (S) => bind(container.element, SerializeToString(S));
    19.             BindableElement element = container.element = BuildField(CurrentValue, fieldLabel, callback);
    20.             return element;
    21.         }
    22.         public abstract BindableElement BuildField(T CurrentValue, string fieldLabel, EventCallback<T> OnChangeCallback);
    23.         public abstract string SerializeToString(T value);
    24.     }
    25.     internal interface CustomUxmlAttributeDescriptionNonGenericMethods {
    26.         BindableElement BuildField(IUxmlAttributes bag, CreationContext cc, string fieldLabel, EventCallback<BindableElement, string> bind);
    27.     }
    28.     internal class BuilderInspectorAttributes : IBuilderInspectorSection {
    29.         BuilderInspector m_Inspector;
    30.         BuilderSelection m_Selection;
    31.         PersistedFoldout m_AttributesSection;
    32.  
    33.         VisualElement currentVisualElement => m_Inspector.currentVisualElement;
    34.  
    35.         public VisualElement root => m_AttributesSection;
    36.  
    37.         public BuilderInspectorAttributes(BuilderInspector inspector) {
    38.             m_Inspector = inspector;
    39.             m_Selection = inspector.selection;
    40.  
    41.             m_AttributesSection = m_Inspector.Q<PersistedFoldout>("inspector-attributes-foldout");
    42.         }
    43.  
    44.         public void Refresh() {
    45.             m_AttributesSection.Clear();
    46.  
    47.             if (currentVisualElement == null)
    48.                 return;
    49.  
    50.             m_AttributesSection.text = currentVisualElement.typeName;
    51.  
    52.             if (m_Selection.selectionType != BuilderSelectionType.Element &&
    53.                 m_Selection.selectionType != BuilderSelectionType.ElementInTemplateInstance)
    54.                 return;
    55.  
    56.             GenerateAttributeFields();
    57.  
    58.             // Forward focus to the panel header.
    59.             m_AttributesSection
    60.                 .Query()
    61.                 .Where(e => e.focusable)
    62.                 .ForEach((e) => m_Inspector.AddFocusable(e));
    63.         }
    64.  
    65.         public void Enable() {
    66.             m_AttributesSection.contentContainer.SetEnabled(true);
    67.         }
    68.  
    69.         public void Disable() {
    70.             m_AttributesSection.contentContainer.SetEnabled(false);
    71.         }
    72.  
    73.         void GenerateAttributeFields() {
    74.             var attributeList = currentVisualElement.GetAttributeDescriptions();
    75.  
    76.             foreach (var attribute in attributeList) {
    77.                 if (attribute == null || attribute.name == null)
    78.                     continue;
    79.  
    80.                 var styleRow = CreateAttributeRow(attribute);
    81.                 m_AttributesSection.Add(styleRow);
    82.             }
    83.         }
    84.  
    85.         BuilderStyleRow CreateAttributeRow(UxmlAttributeDescription attribute) {
    86.             var attributeType = attribute.GetType();
    87.  
    88.             // Generate field label.
    89.             var fieldLabel = BuilderNameUtilities.ConvertDashToHuman(attribute.name);
    90.             BindableElement fieldElement;
    91.             if (attribute is UxmlStringAttributeDescription) {
    92.                 var uiField = new TextField(fieldLabel);
    93.                 if (attribute.name.Equals("name") || attribute.name.Equals("view-data-key"))
    94.                     uiField.RegisterValueChangedCallback(e => {
    95.                         OnValidatedAttributeValueChange(e, BuilderNameUtilities.AttributeRegex, BuilderConstants.AttributeValidationSpacialCharacters);
    96.                     });
    97.                 else if (attribute.name.Equals("binding-path"))
    98.                     uiField.RegisterValueChangedCallback(e => {
    99.                         OnValidatedAttributeValueChange(e, BuilderNameUtilities.BindingPathAttributeRegex, BuilderConstants.BindingPathAttributeValidationSpacialCharacters);
    100.                     });
    101.                 else
    102.                     uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    103.  
    104.                 if (attribute.name.Equals("text")) {
    105.                     uiField.multiline = true;
    106.                     uiField.AddToClassList(BuilderConstants.InspectorMultiLineTextFieldClassName);
    107.                 }
    108.  
    109.                 fieldElement = uiField;
    110.             } else if (attribute is UxmlFloatAttributeDescription) {
    111.                 var uiField = new FloatField(fieldLabel);
    112.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    113.                 fieldElement = uiField;
    114.             } else if (attribute is UxmlDoubleAttributeDescription) {
    115.                 var uiField = new DoubleField(fieldLabel);
    116.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    117.                 fieldElement = uiField;
    118.             } else if (attribute is UxmlIntAttributeDescription) {
    119.                 var uiField = new IntegerField(fieldLabel);
    120.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    121.                 fieldElement = uiField;
    122.             } else if (attribute is UxmlLongAttributeDescription) {
    123.                 var uiField = new LongField(fieldLabel);
    124.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    125.                 fieldElement = uiField;
    126.             } else if (attribute is UxmlBoolAttributeDescription) {
    127.                 var uiField = new Toggle(fieldLabel);
    128.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    129.                 fieldElement = uiField;
    130.             } else if (attribute is UxmlColorAttributeDescription) {
    131.                 var uiField = new ColorField(fieldLabel);
    132.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    133.                 fieldElement = uiField;
    134.             } else if (attributeType.IsGenericType &&
    135.                   !attributeType.GetGenericArguments()[0].IsEnum &&
    136.                   attributeType.GetGenericArguments()[0] is Type) {
    137.                 var uiField = new TextField(fieldLabel);
    138.                 uiField.isDelayed = true;
    139.                 uiField.RegisterValueChangedCallback(e => {
    140.                     OnValidatedTypeAttributeChange(e, attributeType.GetGenericArguments()[0]);
    141.                 });
    142.                 fieldElement = uiField;
    143.             } else if (attributeType.IsGenericType && attributeType.GetGenericArguments()[0].IsEnum) {
    144.                 var propInfo = attributeType.GetProperty("defaultValue");
    145.                 var enumValue = propInfo.GetValue(attribute, null) as Enum;
    146.  
    147.                 // Create and initialize the EnumField.
    148.                 var uiField = new EnumField(fieldLabel);
    149.                 uiField.Init(enumValue);
    150.  
    151.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    152.                 fieldElement = uiField;
    153.             } else if (typeof(CustomUxmlAttributeDescriptionNonGenericMethods).IsAssignableFrom(attribute.GetType())) {
    154.  
    155.                 fieldElement = ((CustomUxmlAttributeDescriptionNonGenericMethods)attribute).BuildField(
    156.                     currentVisualElement.GetVisualElementAsset(), new CreationContext(), fieldLabel, OnAttributeValueChange);
    157.             } else {
    158.                 var uiField = new TextField(fieldLabel);
    159.                 uiField.RegisterValueChangedCallback(OnAttributeValueChange);
    160.                 fieldElement = uiField;
    161.             }
    162.  
    163.             // Create row.
    164.             var styleRow = new BuilderStyleRow();
    165.             styleRow.Add(fieldElement);
    166.  
    167.             // Link the field.
    168.             fieldElement.SetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName, styleRow);
    169.             fieldElement.SetProperty(BuilderConstants.InspectorLinkedAttributeDescriptionVEPropertyName, attribute);
    170.  
    171.             // Set initial value.
    172.             RefreshAttributeField(fieldElement);
    173.  
    174.             // Setup field binding path.
    175.             fieldElement.bindingPath = attribute.name;
    176.  
    177.             // Tooltip.
    178.             var label = fieldElement.Q<Label>();
    179.             if (label != null)
    180.                 label.tooltip = attribute.name;
    181.             else
    182.                 fieldElement.tooltip = attribute.name;
    183.  
    184.             // Context menu.
    185.             fieldElement.AddManipulator(new ContextualMenuManipulator(BuildAttributeFieldContextualMenu));
    186.  
    187.             return styleRow;
    188.         }
    189.  
    190.         object GetCustomValueAbstract(string attributeName) {
    191.             if (currentVisualElement is ScrollView) {
    192.                 var scrollView = currentVisualElement as ScrollView;
    193.                 if (attributeName == "mode") {
    194.                     if (scrollView.ClassListContains(ScrollView.verticalVariantUssClassName))
    195.                         return ScrollViewMode.Vertical;
    196.                     else if (scrollView.ClassListContains(ScrollView.horizontalVariantUssClassName))
    197.                         return ScrollViewMode.Horizontal;
    198.                     else if (scrollView.ClassListContains(ScrollView.verticalHorizontalVariantUssClassName))
    199.                         return ScrollViewMode.VerticalAndHorizontal;
    200.                 } else if (attributeName == "show-horizontal-scroller") {
    201.                     return scrollView.showHorizontal;
    202.                 } else if (attributeName == "show-vertical-scroller") {
    203.                     return scrollView.showVertical;
    204.                 }
    205.             } else if (currentVisualElement is ObjectField objectField) {
    206.                 if (attributeName == "type") {
    207.                     return objectField.objectType;
    208.                 }
    209.             }
    210.  
    211.             return null;
    212.         }
    213.  
    214.         void RefreshAttributeField(BindableElement fieldElement) {
    215.             var styleRow = fieldElement.GetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName) as VisualElement;
    216.             var attribute = fieldElement.GetProperty(BuilderConstants.InspectorLinkedAttributeDescriptionVEPropertyName) as UxmlAttributeDescription;
    217.  
    218.             var veType = currentVisualElement.GetType();
    219.             var camel = BuilderNameUtilities.ConvertDashToCamel(attribute.name);
    220.  
    221.             var fieldInfo = veType.GetProperty(camel, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
    222.  
    223.             object veValueAbstract = null;
    224.             if (fieldInfo == null) {
    225.                 veValueAbstract = GetCustomValueAbstract(attribute.name);
    226.             } else {
    227.                 veValueAbstract = fieldInfo.GetValue(currentVisualElement);
    228.             }
    229.             if (veValueAbstract == null)
    230.                 return;
    231.  
    232.             var attributeType = attribute.GetType();
    233.             var vea = currentVisualElement.GetVisualElementAsset();
    234.  
    235.             if (attribute is UxmlStringAttributeDescription && fieldElement is TextField) {
    236.                 (fieldElement as TextField).SetValueWithoutNotify(GetAttributeStringValue(veValueAbstract));
    237.             } else if (attribute is UxmlFloatAttributeDescription && fieldElement is FloatField) {
    238.                 (fieldElement as FloatField).SetValueWithoutNotify((float)veValueAbstract);
    239.             } else if (attribute is UxmlDoubleAttributeDescription && fieldElement is DoubleField) {
    240.                 (fieldElement as DoubleField).SetValueWithoutNotify((double)veValueAbstract);
    241.             } else if (attribute is UxmlIntAttributeDescription && fieldElement is IntegerField) {
    242.                 if (veValueAbstract is int)
    243.                     (fieldElement as IntegerField).SetValueWithoutNotify((int)veValueAbstract);
    244.                 else if (veValueAbstract is float)
    245.                     (fieldElement as IntegerField).SetValueWithoutNotify(Convert.ToInt32(veValueAbstract));
    246.             } else if (attribute is UxmlLongAttributeDescription && fieldElement is LongField) {
    247.                 (fieldElement as LongField).SetValueWithoutNotify((long)veValueAbstract);
    248.             } else if (attribute is UxmlBoolAttributeDescription && fieldElement is Toggle) {
    249.                 (fieldElement as Toggle).SetValueWithoutNotify((bool)veValueAbstract);
    250.             } else if (attribute is UxmlColorAttributeDescription && fieldElement is ColorField) {
    251.                 (fieldElement as ColorField).SetValueWithoutNotify((Color)veValueAbstract);
    252.             } else if (attributeType.IsGenericType &&
    253.                   !attributeType.GetGenericArguments()[0].IsEnum &&
    254.                   attributeType.GetGenericArguments()[0] is Type &&
    255.                   fieldElement is TextField textField &&
    256.                   veValueAbstract is Type veTypeValue) {
    257.                 var fullTypeName = veTypeValue.AssemblyQualifiedName;
    258.                 var fullTypeNameSplit = fullTypeName.Split(',');
    259.                 textField.SetValueWithoutNotify($"{fullTypeNameSplit[0]},{fullTypeNameSplit[1]}");
    260.             } else if (attributeType.IsGenericType &&
    261.                   attributeType.GetGenericArguments()[0].IsEnum &&
    262.                   fieldElement is EnumField) {
    263.                 var propInfo = attributeType.GetProperty("defaultValue");
    264.                 var enumValue = propInfo.GetValue(attribute, null) as Enum;
    265.  
    266.                 // Create and initialize the EnumField.
    267.                 var uiField = fieldElement as EnumField;
    268.  
    269.                 // Set the value from the UXML attribute.
    270.                 var enumAttributeValueStr = vea?.GetAttributeValue(attribute.name);
    271.                 if (!string.IsNullOrEmpty(enumAttributeValueStr)) {
    272.                     var parsedValue = Enum.Parse(enumValue.GetType(), enumAttributeValueStr, true) as Enum;
    273.                     uiField.SetValueWithoutNotify(parsedValue);
    274.                 }
    275.             } else if (fieldElement is TextField) {
    276.                 (fieldElement as TextField).SetValueWithoutNotify(veValueAbstract.ToString());
    277.             }
    278.  
    279.             styleRow.RemoveFromClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    280.             if (IsAttributeOverriden(attribute))
    281.                 styleRow.AddToClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    282.         }
    283.  
    284.         string GetAttributeStringValue(object attributeValue) {
    285.             string value;
    286.             if (attributeValue is Enum @enum)
    287.                 value = @enum.ToString();
    288.             else if (attributeValue is IList<string> list) {
    289.                 value = string.Join(",", list);
    290.             } else {
    291.                 value = attributeValue.ToString();
    292.             }
    293.  
    294.             return value;
    295.         }
    296.  
    297.         bool IsAttributeOverriden(UxmlAttributeDescription attribute) {
    298.             var vea = currentVisualElement.GetVisualElementAsset();
    299.             if (vea != null && attribute.name == "picking-mode") {
    300.                 var veaAttributeValue = vea.GetAttributeValue(attribute.name);
    301.                 if (veaAttributeValue != null &&
    302.                     veaAttributeValue.ToLower() != attribute.defaultValueAsString.ToLower())
    303.                     return true;
    304.             } else if (attribute.name == "name") {
    305.                 if (!string.IsNullOrEmpty(currentVisualElement.name))
    306.                     return true;
    307.             } else if (vea != null && vea.HasAttribute(attribute.name))
    308.                 return true;
    309.  
    310.             return false;
    311.         }
    312.  
    313.         void ResetAttributeFieldToDefault(BindableElement fieldElement) {
    314.             var styleRow = fieldElement.GetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName) as VisualElement;
    315.             var attribute = fieldElement.GetProperty(BuilderConstants.InspectorLinkedAttributeDescriptionVEPropertyName) as UxmlAttributeDescription;
    316.  
    317.             var attributeType = attribute.GetType();
    318.             var vea = currentVisualElement.GetVisualElementAsset();
    319.  
    320.             if (attribute is UxmlStringAttributeDescription && fieldElement is TextField) {
    321.                 var a = attribute as UxmlStringAttributeDescription;
    322.                 var f = fieldElement as TextField;
    323.                 f.SetValueWithoutNotify(a.defaultValue);
    324.             } else if (attribute is UxmlFloatAttributeDescription && fieldElement is FloatField) {
    325.                 var a = attribute as UxmlFloatAttributeDescription;
    326.                 var f = fieldElement as FloatField;
    327.                 f.SetValueWithoutNotify(a.defaultValue);
    328.             } else if (attribute is UxmlDoubleAttributeDescription && fieldElement is DoubleField) {
    329.                 var a = attribute as UxmlDoubleAttributeDescription;
    330.                 var f = fieldElement as DoubleField;
    331.                 f.SetValueWithoutNotify(a.defaultValue);
    332.             } else if (attribute is UxmlIntAttributeDescription && fieldElement is IntegerField) {
    333.                 var a = attribute as UxmlIntAttributeDescription;
    334.                 var f = fieldElement as IntegerField;
    335.                 f.SetValueWithoutNotify(a.defaultValue);
    336.             } else if (attribute is UxmlLongAttributeDescription && fieldElement is LongField) {
    337.                 var a = attribute as UxmlLongAttributeDescription;
    338.                 var f = fieldElement as LongField;
    339.                 f.SetValueWithoutNotify(a.defaultValue);
    340.             } else if (attribute is UxmlBoolAttributeDescription && fieldElement is Toggle) {
    341.                 var a = attribute as UxmlBoolAttributeDescription;
    342.                 var f = fieldElement as Toggle;
    343.                 f.SetValueWithoutNotify(a.defaultValue);
    344.             } else if (attribute is UxmlColorAttributeDescription && fieldElement is ColorField) {
    345.                 var a = attribute as UxmlColorAttributeDescription;
    346.                 var f = fieldElement as ColorField;
    347.                 f.SetValueWithoutNotify(a.defaultValue);
    348.             } else if (attributeType.IsGenericType &&
    349.                   !attributeType.GetGenericArguments()[0].IsEnum &&
    350.                   attributeType.GetGenericArguments()[0] is Type &&
    351.                   fieldElement is TextField) {
    352.                 var a = attribute as TypedUxmlAttributeDescription<Type>;
    353.                 var f = fieldElement as TextField;
    354.                 if (a.defaultValue == null)
    355.                     f.SetValueWithoutNotify(string.Empty);
    356.                 else
    357.                     f.SetValueWithoutNotify(a.defaultValue.ToString());
    358.             } else if (attributeType.IsGenericType &&
    359.                   attributeType.GetGenericArguments()[0].IsEnum &&
    360.                   fieldElement is EnumField) {
    361.                 var propInfo = attributeType.GetProperty("defaultValue");
    362.                 var enumValue = propInfo.GetValue(attribute, null) as Enum;
    363.  
    364.                 var uiField = fieldElement as EnumField;
    365.                 uiField.SetValueWithoutNotify(enumValue);
    366.             } else if (fieldElement is TextField) {
    367.                 (fieldElement as TextField).SetValueWithoutNotify(string.Empty);
    368.             }
    369.  
    370.             // Clear override.
    371.             styleRow.RemoveFromClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    372.             var styleFields = styleRow.Query<BindableElement>().ToList();
    373.             foreach (var styleField in styleFields) {
    374.                 styleField.RemoveFromClassList(BuilderConstants.InspectorLocalStyleResetClassName);
    375.                 styleField.RemoveFromClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    376.             }
    377.         }
    378.  
    379.         void BuildAttributeFieldContextualMenu(ContextualMenuPopulateEvent evt) {
    380.             evt.menu.AppendAction(
    381.                 BuilderConstants.ContextMenuUnsetMessage,
    382.                 UnsetAttributeProperty,
    383.                 action => {
    384.                     var fieldElement = action.userData as BindableElement;
    385.                     if (fieldElement == null)
    386.                         return DropdownMenuAction.Status.Disabled;
    387.  
    388.                     var attributeName = fieldElement.bindingPath;
    389.                     var vea = currentVisualElement.GetVisualElementAsset();
    390.                     return vea.HasAttribute(attributeName)
    391.                         ? DropdownMenuAction.Status.Normal
    392.                         : DropdownMenuAction.Status.Disabled;
    393.                 },
    394.                 evt.target);
    395.  
    396.             evt.menu.AppendAction(
    397.                 BuilderConstants.ContextMenuUnsetAllMessage,
    398.                 UnsetAllAttributes,
    399.                 action => {
    400.                     var attributeList = currentVisualElement.GetAttributeDescriptions();
    401.                     foreach (var attribute in attributeList) {
    402.                         if (attribute?.name == null)
    403.                             continue;
    404.  
    405.                         if (IsAttributeOverriden(attribute))
    406.                             return DropdownMenuAction.Status.Normal;
    407.                     }
    408.  
    409.                     return DropdownMenuAction.Status.Disabled;
    410.                 },
    411.                 evt.target);
    412.         }
    413.  
    414.         void UnsetAllAttributes(DropdownMenuAction action) {
    415.             var attributeList = currentVisualElement.GetAttributeDescriptions();
    416.  
    417.             // Undo/Redo
    418.             Undo.RegisterCompleteObjectUndo(m_Inspector.visualTreeAsset, BuilderConstants.ChangeAttributeValueUndoMessage);
    419.  
    420.             foreach (var attribute in attributeList) {
    421.                 if (attribute?.name == null)
    422.                     continue;
    423.  
    424.                 // Unset value in asset.
    425.                 var vea = currentVisualElement.GetVisualElementAsset();
    426.                 vea.RemoveAttribute(attribute.name);
    427.             }
    428.  
    429.             var fields = m_AttributesSection.Query<BindableElement>().Where(e => !string.IsNullOrEmpty(e.bindingPath)).ToList();
    430.             foreach (var fieldElement in fields) {
    431.                 // Reset UI value.
    432.                 ResetAttributeFieldToDefault(fieldElement);
    433.             }
    434.  
    435.             // Call Init();
    436.             CallInitOnElement();
    437.  
    438.             // Notify of changes.
    439.             m_Selection.NotifyOfHierarchyChange(m_Inspector);
    440.         }
    441.  
    442.         void UnsetAttributeProperty(DropdownMenuAction action) {
    443.             var fieldElement = action.userData as BindableElement;
    444.             var attributeName = fieldElement.bindingPath;
    445.  
    446.  
    447.             // Undo/Redo
    448.             Undo.RegisterCompleteObjectUndo(m_Inspector.visualTreeAsset, BuilderConstants.ChangeAttributeValueUndoMessage);
    449.  
    450.             // Unset value in asset.
    451.             var vea = currentVisualElement.GetVisualElementAsset();
    452.             vea.RemoveAttribute(attributeName);
    453.  
    454.             // Reset UI value.
    455.             ResetAttributeFieldToDefault(fieldElement);
    456.  
    457.             // Call Init();
    458.             CallInitOnElement();
    459.  
    460.             // Notify of changes.
    461.             m_Selection.NotifyOfHierarchyChange(m_Inspector);
    462.         }
    463.  
    464.         void OnAttributeValueChange(ChangeEvent<string> evt) {
    465.             var field = evt.target as BindableElement;
    466.             PostAttributeValueChange(field, evt.newValue);
    467.         }
    468.  
    469.         void OnAttributeValueChange(BindableElement field, string newValue) {
    470.             PostAttributeValueChange(field, newValue);
    471.         }
    472.  
    473.         void OnValidatedTypeAttributeChange(ChangeEvent<string> evt, Type desiredType) {
    474.             var field = evt.target as TextField;
    475.             var typeName = evt.newValue;
    476.             var fullTypeName = typeName;
    477.             if (!string.IsNullOrEmpty(typeName)) {
    478.                 var type = Type.GetType(fullTypeName, false);
    479.  
    480.                 // Try some auto-fixes.
    481.                 if (type == null) {
    482.                     fullTypeName = typeName + ", UnityEngine.CoreModule";
    483.                     type = Type.GetType(fullTypeName, false);
    484.                 }
    485.                 if (type == null) {
    486.                     fullTypeName = typeName + ", UnityEditor";
    487.                     type = Type.GetType(fullTypeName, false);
    488.                 }
    489.                 if (type == null && typeName.Contains(".")) {
    490.                     var split = typeName.Split('.');
    491.                     fullTypeName = typeName + $", {split[0]}.{split[1]}Module";
    492.                     type = Type.GetType(fullTypeName, false);
    493.                 }
    494.  
    495.                 if (type == null) {
    496.                     Builder.ShowWarning(string.Format(BuilderConstants.TypeAttributeInvalidTypeMessage, field.label));
    497.                     evt.StopPropagation();
    498.                     return;
    499.                 } else if (!desiredType.IsAssignableFrom(type)) {
    500.                     Builder.ShowWarning(string.Format(BuilderConstants.TypeAttributeMustDeriveFromMessage, field.label, desiredType.FullName));
    501.                     evt.StopPropagation();
    502.                     return;
    503.                 }
    504.             }
    505.  
    506.             field.SetValueWithoutNotify(fullTypeName);
    507.             PostAttributeValueChange(field, fullTypeName);
    508.         }
    509.  
    510.         void OnValidatedAttributeValueChange(ChangeEvent<string> evt, Regex regex, string message) {
    511.             var field = evt.target as TextField;
    512.             if (!string.IsNullOrEmpty(evt.newValue) && !regex.IsMatch(evt.newValue)) {
    513.                 Builder.ShowWarning(string.Format(message, field.label));
    514.                 field.SetValueWithoutNotify(evt.previousValue);
    515.                 evt.StopPropagation();
    516.                 return;
    517.             }
    518.  
    519.             OnAttributeValueChange(evt);
    520.         }
    521.  
    522.         void OnAttributeValueChange(ChangeEvent<float> evt) {
    523.             var field = evt.target as FloatField;
    524.             PostAttributeValueChange(field, evt.newValue.ToString());
    525.         }
    526.  
    527.         void OnAttributeValueChange(ChangeEvent<double> evt) {
    528.             var field = evt.target as DoubleField;
    529.             PostAttributeValueChange(field, evt.newValue.ToString());
    530.         }
    531.  
    532.         void OnAttributeValueChange(ChangeEvent<int> evt) {
    533.             var field = evt.target as IntegerField;
    534.             PostAttributeValueChange(field, evt.newValue.ToString());
    535.         }
    536.  
    537.         void OnAttributeValueChange(ChangeEvent<long> evt) {
    538.             var field = evt.target as LongField;
    539.             PostAttributeValueChange(field, evt.newValue.ToString());
    540.         }
    541.  
    542.         void OnAttributeValueChange(ChangeEvent<bool> evt) {
    543.             var field = evt.target as Toggle;
    544.             PostAttributeValueChange(field, evt.newValue.ToString().ToLower());
    545.         }
    546.  
    547.         void OnAttributeValueChange(ChangeEvent<Color> evt) {
    548.             var field = evt.target as ColorField;
    549.             PostAttributeValueChange(field, "#" + ColorUtility.ToHtmlStringRGBA(evt.newValue));
    550.         }
    551.  
    552.         void OnAttributeValueChange(ChangeEvent<Enum> evt) {
    553.             var field = evt.target as BindableElement;
    554.             PostAttributeValueChange(field, evt.newValue.ToString());
    555.         }
    556.  
    557.         void PostAttributeValueChange(BindableElement field, string value) {
    558.             // Undo/Redo
    559.             Undo.RegisterCompleteObjectUndo(m_Inspector.visualTreeAsset, BuilderConstants.ChangeAttributeValueUndoMessage);
    560.  
    561.             // Set value in asset.
    562.             var vea = currentVisualElement.GetVisualElementAsset();
    563.             vea.SetAttributeValue(field.bindingPath, value);
    564.  
    565.             // Mark field as overridden.
    566.             var styleRow = field.GetProperty(BuilderConstants.InspectorLinkedStyleRowVEPropertyName) as BuilderStyleRow;
    567.             styleRow.AddToClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    568.  
    569.             var styleFields = styleRow.Query<BindableElement>().ToList();
    570.  
    571.             foreach (var styleField in styleFields) {
    572.                 styleField.RemoveFromClassList(BuilderConstants.InspectorLocalStyleResetClassName);
    573.                 if (field.bindingPath == styleField.bindingPath) {
    574.                     styleField.AddToClassList(BuilderConstants.InspectorLocalStyleOverrideClassName);
    575.                 } else if (!string.IsNullOrEmpty(styleField.bindingPath) &&
    576.                       field.bindingPath != styleField.bindingPath &&
    577.                       !styleField.ClassListContains(BuilderConstants.InspectorLocalStyleOverrideClassName)) {
    578.                     styleField.AddToClassList(BuilderConstants.InspectorLocalStyleResetClassName);
    579.                 }
    580.             }
    581.  
    582.             // Call Init();
    583.             CallInitOnElement();
    584.  
    585.             // Notify of changes.
    586.             m_Selection.NotifyOfHierarchyChange(m_Inspector);
    587.         }
    588.  
    589.         void CallInitOnElement() {
    590.             var fullTypeName = currentVisualElement.GetType().ToString();
    591.  
    592.             if (VisualElementFactoryRegistry.TryGetValue(fullTypeName, out var factoryList)) {
    593.                 var traits = factoryList[0].GetTraits();
    594.  
    595.                 if (traits == null)
    596.                     return;
    597.  
    598.                 var context = new CreationContext();
    599.                 var vea = currentVisualElement.GetVisualElementAsset();
    600.  
    601.                 try {
    602.                     traits.Init(currentVisualElement, vea, context);
    603.                 } catch {
    604.                     // HACK: This throws in 2019.3.0a4 because usageHints property throws when set after the element has already been added to the panel.
    605.                 }
    606.             }
    607.         }
    608.     }
    609. }
    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using Unity.UI.Builder;
    4. using UnityEditor.UIElements;
    5. using UnityEngine.UIElements;
    6.  
    7. public class UxmlTypeAttributeDescription : CustomUxmlAttributeDescription<Type> {
    8.     public UxmlTypeAttributeDescription() {
    9.         type = "Type";
    10.         typeNamespace = xmlSchemaNamespace;
    11.         defaultValue = typeof(Type);
    12.     }
    13.  
    14.     public override string defaultValueAsString { get { return defaultValue.AssemblyQualifiedName; } }
    15.  
    16.     public override BindableElement BuildField(Type CurrentValue, string fieldLabel, EventCallback<Type> OnChangeCallback) {
    17.         Type[] types = Utils.GetAllClassesExtending<Manipulator>("Unity", "Microsoft", "System", "Mono", "UMotion");
    18.         Array.Sort(types, (T1, T2) => T2.FullName.CompareTo(T1.FullName));
    19.         Func<Type, string> func = (T) => (T.ToString());
    20.         PopupField<Type> textField = new PopupField<Type>(fieldLabel, types.ToList(), types[0], func, func);
    21.         if (types.Contains(CurrentValue)) {
    22.             textField.value = CurrentValue;
    23.         }
    24.         textField.RegisterValueChangedCallback((T) => OnChangeCallback(T.newValue));
    25.         return textField;
    26.     }
    27.  
    28.     public override string SerializeToString(Type value) {
    29.         return value.AssemblyQualifiedName;
    30.     }
    31.  
    32.     public override Type GetValueFromBag(IUxmlAttributes bag, CreationContext cc) {
    33.         return GetValueFromBag(bag, cc, (s, t) => {
    34.             Type tp = Type.GetType(s);
    35.             if (tp != null) {
    36.                 return tp;
    37.             }
    38.             return t;
    39.         }, defaultValue);
    40.     }
    41.  
    42.     public bool TryGetValueFromBag(IUxmlAttributes bag, CreationContext cc, ref Type value) {
    43.         return TryGetValueFromBag(bag, cc, (s, t) => {
    44.             Type tp = Type.GetType(s);
    45.             if (tp != null) {
    46.                 return tp;
    47.             }
    48.             return t;
    49.         }, defaultValue, ref value);
    50.     }
    51. }
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine.UIElements;
    4.  
    5. class AddManipulator : VisualElement {
    6.     public Type ManipulatorType { get; set; }
    7.  
    8.     public new class UxmlFactory : UxmlFactory<AddManipulator, UxmlTraits> { }
    9.  
    10.     public new class UxmlTraits : VisualElement.UxmlTraits {
    11.         public UxmlTypeAttributeDescription type = new UxmlTypeAttributeDescription { name = "Manipulator-Type", defaultValue = typeof(Type) };
    12.         public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription {
    13.             get { yield break; }
    14.         }
    15.  
    16.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) {
    17.             base.Init(ve, bag, cc);
    18.             AddManipulator addm = ve as AddManipulator;
    19.             addm.ManipulatorType = type.GetValueFromBag(bag, cc);
    20.         }
    21.     }
    22.  
    23. }
    Next I will make a version where you get the whole UIElement parent as a way to enable a custom field take into consideration other fields to decide it's value. So I can make a sub inspector for the manipulator selected.
     
  10. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    :)

    I haven't spent too much time looking into the specifics but I agree it won't be trivial. The initial idea was actually to just use the Editor class and CreateInspectorGUI() for your entire custom VisualElement, not per-field. You would then do all the string conversions yourself for any custom UXML attributes in your Editor class impl. There's also the fact that VisualElements are not GameObject (or ScriptableObjects of any kind) which means we'd need to create a fake one for the Editor to attach itself to. So for now, your solution seems more reasonable for your needs.

    Thanks for posting your solution. I'm sure it will be useful for other users in a similar bind. Not sure how much, if any, we'll use in the final implementation in the Builder but it serves as a good bump in our relative priorities.
     
    Guedez likes this.
  11. dasgandlaf

    dasgandlaf

    Joined:
    Aug 13, 2020
    Posts:
    23
    Is there still no integrated way of doing this?
     
    BlackSpider and sewy like this.
  12. endink

    endink

    Joined:
    Jan 16, 2022
    Posts:
    1
    we are waiting for this feature, any good news for this?
     
  13. FoodFish_

    FoodFish_

    Joined:
    May 3, 2018
    Posts:
    58
  14. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    780
    We are well aware of this problem, but it requires major changes in the UXML serialization and UI Builder implementation. The good news is that we are actually working on these changes, but it won't reach a Unity version before a while (the earliest estimate would be 2023 LTS).
     
  15. FoodFish_

    FoodFish_

    Joined:
    May 3, 2018
    Posts:
    58
    Good to hear, thank you!
     
  16. levius

    levius

    Joined:
    Jun 18, 2016
    Posts:
    19
    Hello. Have any news for this feature? Extentions for UIBuilder like Editor Extentions it's very important feature for using it in production.

    I really want something similar to PropertyDrawer and EditorWindow.ShowAsDropDown.
    UxmlAssetAttributeDescription already there.
     
  17. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    All we can say for now is that the team is actively working on exactly this right now. Aiming for 2023 LTS, but that's not a guarantee at this point.
     
  18. xXPancakeXx

    xXPancakeXx

    Joined:
    Mar 14, 2015
    Posts:
    55
    Any news on this feature? We also would really appreciate that.
     
    FoodFish_ and Kano_Mario like this.
  19. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    480
    We ended up releasing a feature called Uxml Serialization, which allows users to get rid of the factory and traits classes for their custom element. Using this, you could take an element that looks like this:

    Code (CSharp):
    1. public class MyElement : VisualElement
    2. {
    3.     new class UXMLFactory : UXMLFactory<MyElement, UXMLTraits>
    4.     {
    5.     }
    6.  
    7.     new class UXMLTraits : VisualElement.UXMLTraits
    8.     {
    9.         private UXMLFloatAttributeDescription m_Value = new UXMLFloatAttributeDescription {name = "value"};
    10.  
    11.         public override void Init(VisualElement ve, IUXMLAttributes bag, CreationContext cc)
    12.         {
    13.             base.Init(ve, bag, cc);
    14.             var baseField = (MyElement) ve;
    15.             baseField.value = m_Value.GetValueFromBag(bag, cc);
    16.         }
    17.     }
    18.  
    19.     public float value { get; set; }
    20. }
    And transform it into this:

    Code (CSharp):
    1. [UXMLElement]
    2. public partial class MyElement : VisualElement
    3. {
    4.     [UXMLAttribute]
    5.     public float value { get; set; }
    6. }
    The main advantages of this new approach are:
    * Much reduced boilerplate code required in order to use an element in Uxml.
    * Instead of parsing the text of the attribute at runtime to convert it into the correct type, we can do this at import time.
    * To display these fields in the UI Builder inspector, we are now using a
    PropertyField
    , meaning that you can define property drawers for the fields of your element.

    We'll make an announcement about this soon to provide more details.
    Hope this helps!
     
    Kirsche, achimmihca, neoRiley and 5 others like this.
  20. xXPancakeXx

    xXPancakeXx

    Joined:
    Mar 14, 2015
    Posts:
    55
    Nice, but how does this handle custom classes? Can we then somehow create an editor for it?
     
  21. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    480
    At the moment, it is only possible to define property drawers for the attributes of the custom element. You can use field/property attributes to drive the property drawer if you need to override the "default" ones.
     
  22. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Sounds like it completely solves the issue to me. I imagine I can make a custom property drawer that has a string field for a filter and a drop down for all assembly types that extend Manipulator and whose name contains the contents of the filter field.
    Shame I can't update my project for a good year or more.
     
  23. FoodFish_

    FoodFish_

    Joined:
    May 3, 2018
    Posts:
    58
  24. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    480
    Yes, it will let you reference a
    UnityEngine.Object
    based asset in your UXML.
     
    achimmihca and FoodFish_ like this.
  25. swingingtom

    swingingtom

    Joined:
    Feb 15, 2018
    Posts:
    10
    @martinpa_unity can you tell more about the feature called Uxml Serialization ? I didn't succeed to find any other informations except here. Thanks !
     
  26. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    480
    Hi @swingingtom, you can find information about this feature in the manual here as it's only available in version 2023.2 and up. The name Uxml Serialization is more like a codename that we use for it, as it enables to parse and convert uxml attributes to their correct types at import time and save them in the visual tree asset using the Unity serialization.

    Hope this helps!
     
    swingingtom likes this.
  27. swingingtom

    swingingtom

    Joined:
    Feb 15, 2018
    Posts:
    10
    It definitely would. Thanks.

    This looks like a smart choice you did guys to ease custom attributes. Im just looking for paths now and the whole traits, etc, even if it looks like proper oop seemed over complicated for most cases. If you have a dedicated channel, I would be pleased to post feedbacks.
     
  28. martinpa_unity

    martinpa_unity

    Unity Technologies

    Joined:
    Oct 18, 2017
    Posts:
    480
    The UxmlFactory/UxmlTraits classes were added because we lacked two main features in Unity at the time:
    *
    [SerializeReference]
    , which allows us to save arbitrary types as reference types instead of value types.
    * Source Generators, which allows us to generate the boilerplate code needed instead of asking users to do it.

    What we opted for at the time was to serialize the text of the uxml attribute and then parse it when instantiating the uxml because we couldn't do it at import time. So it wasn't really a question of proper oop, but more a question of "this is what we need to do".

    This forum is as good place as any.
    You can start a new thread with your feedback :)
     
  29. neoRiley

    neoRiley

    Joined:
    Dec 12, 2008
    Posts:
    162
    God Bless you for this!