Search Unity

How To Create A Layout Visual Element? (lifecycle)

Discussion in 'UI Toolkit' started by Beks_Omega, Apr 15, 2019.

  1. Beks_Omega

    Beks_Omega

    Joined:
    Jun 7, 2017
    Posts:
    72
    Hello,

    I wanted to try to create a VisualElement that would control the positioning of its child elements. Following this documentation I added the custom subclass and it is now showing up in the debugger (Yay!).

    My problem is that I don't know where to hook into the layouting lifecycle (if there even is anywhere to hook in since the layouting also needs to interact with the USS). I looked at the UxmlTraits Init() function, but elements are initialized from top to bottom (i.e. the child elements wouldn't be ready to be layed out), so that's not much help.

    Is the recommended course of action to create a CustomInit() function that the EditorWindow would call? Or is there another option?

    Thank you so much for taking the time to read this!
    BeksOmega
     
  2. jonathanma_unity

    jonathanma_unity

    Unity Technologies

    Joined:
    Jan 7, 2019
    Posts:
    229
    Hi,

    You can register a callback on the GeometryChangedEvent to know when the layout of the element has changed.

    Code (CSharp):
    1. parentElement.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
    2. private void OnGeometryChanged(GeometryChangedEvent evt)
    3. {
    4.     // Check if dimension has changed
    5.     if (evt.oldRect.size == evt.newRect.size)
    6.        return;
    7.    
    8.     // Size has change update child positions
    9. }
    10.  
    However, from what you're describing it looks like you want to offset the child elements... This can be done purely with styles without the needs of doing it "manually" like above.
     
  3. Beks_Omega

    Beks_Omega

    Joined:
    Jun 7, 2017
    Posts:
    72
    Thank you for the reply! I'm sorry it took so long for me to get back to you!

    I wanted to see if I could create a visual element to do layouting that isn't available with styles (or is at least quite complex to do manually). For example this (unoptimized) RadialLayout VisualElement, which positions child elements in a circle around it:
    Code (CSharp):
    1. public class RadialLayout : VisualElement
    2.     {
    3.         private int childTop;
    4.         private int childLeft;
    5.  
    6.         public RadialLayout () { }
    7.  
    8.         public class Factory : UxmlFactory<RadialLayout , Traits> { }
    9.  
    10.         public class Traits : UxmlTraits
    11.         {
    12.             UxmlIntAttributeDescription m_ChildTopTrait =
    13.                 new UxmlIntAttributeDescription { name = "childTop" };
    14.             UxmlIntAttributeDescription m_ChildLeftTrait =
    15.                 new UxmlIntAttributeDescription { name = "childLeft" };
    16.  
    17.             public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
    18.             {
    19.                 get
    20.                 {
    21.                     yield return new UxmlChildElementDescription(typeof(VisualElement));
    22.                 }
    23.             }
    24.  
    25.             public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    26.             {
    27.                 base.Init(ve, bag, cc);
    28.                 RadialLayout radialLayout = (RadialLayout )ve;
    29.                 RadialLayout .childTop = m_ChildTopTrait.GetValueFromBag(bag, cc);
    30.                 RadialLayout .childLeft = m_ChildLeftTrait.GetValueFromBag(bag, cc);
    31.             }
    32.         }
    33.        
    34.         protected override void ExecuteDefaultAction(EventBase evt)
    35.         {
    36.             base.ExecuteDefaultAction(evt);
    37.             if (evt.eventTypeId == GeometryChangedEvent.TypeId())
    38.             {
    39.                 Debug.Log("geometry changed");
    40.  
    41.                 var geomEvt = (GeometryChangedEvent)evt;
    42.                 var radius = Mathf.Min(geomEvt.newRect.width / 2, geomEvt.newRect.height / 2);
    43.                 var center = geomEvt.newRect.center;
    44.                 for (int i = 1; i < childCount + 1; i++)
    45.                 {
    46.                     var element = ElementAt(i - 1);
    47.                     var relativeToCenter = new Vector2(
    48.                         center.x + (radius * Mathf.Cos((2 * i * Mathf.PI) / childCount)),
    49.                         center.y + (radius * Mathf.Sin((2 * i * Mathf.PI) / childCount)));
    50.                     element.style.position = Position.Absolute;
    51.                     element.style.left = relativeToCenter.x;
    52.                     element.style.top = relativeToCenter.y;
    53.                 }
    54.             }
    55.         }
    56.     }
    I do wish there was a way to listen for when an elements display/visibility changes so I could update my layout (or maybe this does exist and I missed it as well) but for now I think this will work perfectly!

    Thank you again for the help!
    BeksOmega