Search Unity

Custom VisualElement from Template

Discussion in 'UI Toolkit' started by reigota, Sep 24, 2020.

  1. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    Hi all!


    I am trying to create some custom visual elements but I am struggling a lot with something (I hope) is simple but I am missing.

    Lets suppose I want to create a label with an icon in the same component. Something like this:

    upload_2020-9-24_15-8-37.png

    This is the uxml for such template:
    Code (xml):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
    2.     <ui:VisualElement name="UIRichLabel" style="flex-direction: row;">
    3.         <ui:VisualElement name="icon" class="error_icon" />
    4.         <ui:Label text="LABEL" name="label" style="font-size: 12px;" />
    5.     </ui:VisualElement>
    6. </ui:UXML>
    7.  
    I created 3 css classes that I will use to change the icon.

    Seems simple. And it is supposed to be simple. If I drag and drop this template in my editor tool, it works fine. But I want something better: I want to change the label via UI Builder inspector! Something the template does not allow me to do. Solution: template->custom widget.

    I wrote the following class:

    Code (CSharp):
    1. public class UIRichLabel : VisualElement
    2. {
    3.     private Label m_label = default;
    4.     public string Label { get; set; }
    5.  
    6.     public new class UxmlFactory : UxmlFactory<UIRichLabel, UxmlTraits> { }
    7.  
    8.     public new class UxmlTraits : VisualElement.UxmlTraits
    9.     {
    10.         UxmlStringAttributeDescription m_label = new UxmlStringAttributeDescription { name = "label", defaultValue = "Label" };
    11.         public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    12.         {
    13.             base.Init(ve, bag, cc);
    14.  
    15.             UIRichLabel uIRichLabel = ve as UIRichLabel;
    16.  
    17.             uIRichLabel.Clear();
    18.             uIRichLabel.Label = m_label.GetValueFromBag(bag, cc);
    19.  
    20.             VisualTreeAsset template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UIRichLabelTemplate.uxml");
    21.             template.CloneTree(uIRichLabel);
    22.  
    23.             uIRichLabel.Init();
    24.         }
    25.     }
    26.  
    27.     public UIRichLabel()
    28.     {
    29.     }
    30.  
    31.     public void Init()
    32.     {
    33.         m_label = this.Q<Label>(LABEL_SELECTOR);
    34.     }
    35.  
    36. }

    This is almost working. Except by the fact that any change in inspector is not reflected in the label (inside the richlabel).

    Any hints here? How to bind them?
     
    Oneiros90 likes this.
  2. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    700
    Hello, you should make the property on your line 4 use the m_Label, like this:

    Code (CSharp):
    1. private Label m_label = default;
    2. public string Label
    3. {
    4.    get
    5.    {
    6.       return m_label;
    7.    }
    8.    set
    9.    {
    10.       m_label = value;
    11.    }
    12. }
    This is currently a limitation on the UI Builder, so you have to make sure you have an accessor to to your member, and the names match, so that you can use it in the UI.

    Hope it helps!
     
  3. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    I managed to get it working. It needs to change m_label.text and it is working now.

    But I am facing another issue.
    I want to expose a few style properties of my internal Label (like font size)

    I am trying this way:

    Code (CSharp):
    1. (UxmlTraits)
    2. UxmlIntAttributeDescription m_textSize = new UxmlIntAttributeDescription { name = "text-size", defaultValue = 12 };
    3. uIRichLabel.TextSize = m_textSize.GetValueFromBag(bag, cc);
    4.  
    5. (my component)
    6.     public int TextSize
    7.     {
    8.         get { return m_label.style.unityFont.value.fontSize; }
    9.         set { m_label.style.unityFont.value.fontSize = value; }
    10.     }
    But the m_label.style is read-only..
     
  4. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    By the way, the problem was another one. It was the order of initialization.

    I changed this

    Code (CSharp):
    1. UIRichLabel uIRichLabel = ve as UIRichLabel;
    2.             uIRichLabel.Clear();
    3.             uIRichLabel.Label = m_label.GetValueFromBag(bag, cc);
    4.             VisualTreeAsset template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UIRichLabelTemplate.uxml");
    5.             template.CloneTree(uIRichLabel);
    6.             uIRichLabel.Init();

    to this

    Code (CSharp):
    1.             UIRichLabel uIRichLabel = ve as UIRichLabel;
    2.             uIRichLabel.Clear();
    3.  
    4.             VisualTreeAsset template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/FTUE/UIRichLabelTemplate.uxml");
    5.             template.CloneTree(uIRichLabel);
    6.  
    7.             uIRichLabel.Init();
    8.  
    9.             uIRichLabel.Label = m_label.GetValueFromBag(bag, cc);
    10.  
    my init code

    Code (CSharp):
    1.  public void Init()
    2.     {
    3.      
    4.         m_label = this.Q<Label>(LABEL_SELECTOR);
    5.     }
    and my property
    Code (CSharp):
    1. [code=CSharp]private Label m_label = default;
    2. public string Label
    3. {
    4.    get
    5.    {
    6.       return m_label.text;
    7.    }
    8.    set
    9.    {
    10.       m_label.text = value;
    11.    }
    12. }
    [/code]

    And started to work :)
     
  5. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    Rule of thumb, the UxmlTraits.Init should be seen as a facultative way of setting parameters of your elements.

    The lines where you use CloneTree, then call Init() to find the inner Label element should be moved into your constructor. This way, you probably won't need to call Clear() and instancing your RichLabel from c# using new will work as expected.
     
    reigota likes this.
  6. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    Thanks, i will try that.

    And about the font style? how do i change it in my c#?
     
  7. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    you can do myLabel.style.fontSize = 16;
     
  8. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    Unity complains that style is read only
     
  9. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    you can't change the size of a loaded Font, style.unityfont can only be assigned Font objects directly.

    You can write to style.fontSize
     
    Last edited: Sep 24, 2020
  10. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    upload_2020-9-25_13-30-10.png

    I cant @uMathieu ..
     
  11. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    upload_2020-9-25_13-33-41.png

    This way it works :)
     
  12. reigota

    reigota

    Joined:
    Mar 23, 2010
    Posts:
    86
    I did that and it is working lovely, thanks
     
    uMathieu likes this.
  13. Vander-Does

    Vander-Does

    Joined:
    Dec 22, 2015
    Posts:
    19
    @uMathieu is there a complete example of a custom VisualElement with a template? I'm finding it difficult to piece together best practices from the docs and this thread.
     
  14. IndieForger

    IndieForger

    Joined:
    Dec 31, 2012
    Posts:
    92
    thread bump.

    I am just stepping through the code I could find at https://github.com/needle-mirror/com.unity.ui.
    Simplest element inheriting from VisualElement seems to be BindableElement.


    Code (CSharp):
    1. public class FloatField : TextValueField<float>
    2.     ↓
    3. public abstract class TextValueField<TValueType> : TextInputBaseField<TValueType>, IValueField<TValueType>
    4.     ↓
    5. public abstract class TextInputBaseField<TValueType> : BaseField<TValueType>
    6.     ↓
    7. public abstract class BaseField<TValueType> : BindableElement, INotifyValueChanged<TValueType>, IMixedValueSupport
    8.     ↓
    9. public class BindableElement : VisualElement, IBindable
    Would be great to have a bit of a walk through or an examples of fully functional custom element that extends from either VisualElement of BindableElement.
     
    Last edited: Dec 2, 2022