Search Unity

Is UIElements much more work for simple editors or am I not using the API efficiently?

Discussion in 'UIElements' started by Xarbrough, Jul 3, 2019.

  1. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    424
    I'm already convinced that UIElements will be great for complex editor windows and special custom editors, however, after trying to convert a few simple editor tools I kind of think that it will be much more code and honestly more work to use UIElements.

    Is this only my perception or would you agree that UIElements is better suited for more complex cases?

    As an example, here is my old IMGUI code:

    Code (CSharp):
    1. namespace MyNamespace
    2. {
    3.     using UnityEditor;
    4.     using UnityEngine;
    5.  
    6.     public class TimeScaleWindow : EditorWindow
    7.     {
    8.         private const string windowTitle = "Time Scale";
    9.  
    10.         [MenuItem("Puk/" + windowTitle)]
    11.         public static void ShowWindow()
    12.         {
    13.             var window = GetWindow<TimeScaleWindow>(windowTitle);
    14.             window.minSize = new Vector2(30f, EditorGUIUtility.singleLineHeight + 2f);
    15.         }
    16.  
    17.         private float maxTime = 10f;
    18.         private float timeScale;
    19.         private GUIContent resetIcon;
    20.         private GUIStyle resetButtonStyle;
    21.  
    22.         private void OnEnable()
    23.         {
    24.             timeScale = Time.timeScale;
    25.             var icon = (Texture2D)(EditorGUIUtility.isProSkin ?
    26.                 EditorGUIUtility.Load("d_LookDevResetEnv") : EditorGUIUtility.Load("LookDevResetEnv"));
    27.             resetIcon = new GUIContent(icon);
    28.         }
    29.  
    30.         private void OnDisable()
    31.         {
    32.             timeScale = 1f;
    33.         }
    34.  
    35.         private void OnGUI()
    36.         {
    37.             if (resetButtonStyle == null)
    38.             {
    39.                 resetButtonStyle = new GUIStyle(EditorStyles.miniButton)
    40.                 {
    41.                     padding = new RectOffset(),
    42.                     margin = new RectOffset()
    43.                 };
    44.             }
    45.  
    46.             GUI.enabled = Application.isPlaying;
    47.  
    48.             Rect rect = EditorGUILayout.GetControlRect();
    49.             rect.width -= 59;
    50.  
    51.             EditorGUI.BeginChangeCheck();
    52.             timeScale = EditorGUI.Slider(rect, GUIContent.none, timeScale, 0f, maxTime);
    53.             if (EditorGUI.EndChangeCheck())
    54.                 Time.timeScale = timeScale;
    55.  
    56.             rect.x = rect.xMax + 4;
    57.             rect.width = 30;
    58.             maxTime = EditorGUI.FloatField(rect, GUIContent.none, maxTime);
    59.             if (maxTime < 0)
    60.                 maxTime = 0f;
    61.  
    62.             rect.x = rect.xMax + 3;
    63.             rect.width = 21;
    64.  
    65.             if (GUI.Button(rect, resetIcon, resetButtonStyle))
    66.             {
    67.                 Time.timeScale = 1f;
    68.                 timeScale = 1f;
    69.             }
    70.  
    71.             GUI.enabled = true;
    72.         }
    73.     }
    74. }
    It's really no masterpiece, but it only took 5-10 minutes to write and does what it needs to do, looks fine and all.

    Here is the same window in UIElements:

    Code (CSharp):
    1. namespace MyNamespace
    2. {
    3.     using System;
    4.     using UnityEditor;
    5.     using UnityEditor.UIElements;
    6.     using UnityEngine;
    7.     using UnityEngine.UIElements;
    8.  
    9.     public class TimeScaleWindow_Elements : EditorWindow
    10.     {
    11.         private const string windowTitle = "Time Scale";
    12.  
    13.         [MenuItem("Puk/" + windowTitle + " (Elements)")]
    14.         public static void ShowWindow()
    15.         {
    16.             var window = GetWindow<TimeScaleWindow_Elements>();
    17.             window.minSize = new Vector2(30f, EditorGUIUtility.singleLineHeight + 6f);
    18.             window.titleContent = new GUIContent(windowTitle);
    19.         }
    20.  
    21.         [SerializeField]
    22.         private StyleSheet styleSheet;
    23.  
    24.         private void OnEnable()
    25.         {
    26.             EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
    27.  
    28.             var root = base.rootVisualElement;
    29.             root.styleSheets.Add(this.styleSheet);
    30.  
    31.             var container = new VisualElement();
    32.             container.name = "Container";
    33.             container.SetEnabled(Application.isPlaying);
    34.  
    35.             var slider = new Slider(0f, 10f);
    36.             slider.value = (float)Math.Round(Time.timeScale, 2);
    37.             container.Add(slider);
    38.  
    39.             var timeField = new FloatField(3);
    40.             timeField.value = slider.value;
    41.             timeField.RegisterValueChangedCallback(evt =>
    42.             {
    43.                 float value = (float)Math.Round(Mathf.Max(0, evt.newValue), 2);
    44.                 Time.timeScale = value;
    45.                 slider.value = value;
    46.             });
    47.             container.Add(timeField);
    48.  
    49.             slider.RegisterValueChangedCallback(evt =>
    50.             {
    51.                 float value = (float)Math.Round(Mathf.Max(0, evt.newValue), 2);
    52.                 Time.timeScale = value;
    53.                 timeField.SetValueWithoutNotify(value);
    54.             });
    55.  
    56.             var maxTimeField = new FloatField(3);
    57.             maxTimeField.value = slider.highValue;
    58.             maxTimeField.RegisterValueChangedCallback(evt =>
    59.             {
    60.                 slider.highValue = evt.newValue;
    61.             });
    62.             container.Add(maxTimeField);
    63.  
    64.             var button = new Button();
    65.             var icon = new VisualElement();
    66.             button.clickable.clicked += () =>
    67.             {
    68.                 Time.timeScale = 1f;
    69.                 slider.value = 1f;
    70.                 timeField.SetValueWithoutNotify(1f);
    71.             };
    72.             button.Add(icon);
    73.             container.Add(button);
    74.  
    75.             root.Add(container);
    76.         }
    77.  
    78.         private void OnPlayModeStateChanged(PlayModeStateChange mode)
    79.         {
    80.             if (mode == PlayModeStateChange.EnteredPlayMode)
    81.                 rootVisualElement.Q("Container").SetEnabled(true);
    82.             else if (mode == PlayModeStateChange.ExitingPlayMode)
    83.                 rootVisualElement.Q("Container").SetEnabled(false);
    84.         }
    85.  
    86.         private void OnDisable()
    87.         {
    88.             Time.timeScale = 1f;
    89.             EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
    90.         }
    91.     }
    92. }
    And the required stylesheet:

    Code (CSharp):
    1. #Container > * {
    2.     margin-left: 2;
    3.     margin-right: 2;
    4. }
    5.  
    6. #Container {
    7.     flex-direction: row;
    8.     margin-top: 2;
    9.     margin-bottom: 2;
    10. }
    11.  
    12. FloatField {
    13.     width: 40;
    14.     margin-left: 20;
    15.     align-self: center;
    16. }
    17.  
    18. Slider {
    19.     flex-grow: 1;
    20.     padding-left: 2;
    21.     padding-right: 2;
    22. }
    23.  
    24. Button {
    25.     width: 21;
    26.     height: 19;
    27.     padding-top: 2;
    28.     margin-top: 0;
    29. }
    30.  
    31. Button > VisualElement {
    32.     width: 12;
    33.     height: 12;
    34.     background-image: resource("d_LookDevResetEnv");
    35. }
    This took much longer to write and still isn't perfect (something about the margins and padding is still off when scaling, etc.).

    Please let me know if I simply haven't grasped the full power of the new API or see this as my feedback that we need more high-level functionality.

    I also am aware of the fact that I can call IMGUI code from within UIElements, but as far as I understand it, this feature is only for transitioning and the end goal will be to convert everything to the new system, so I'm not comparing shortcuts like this.

    Here are a few points I think are lacking:
    • Enabling/Disabling elements depending on the playmode state is much easier in IMGUI, because I simply update a single value GUI.enabled each frame
    • The same goes for hiding and unhiding elements depending on some state (e.g. only show a portion of the UI if a bool is checked)
    • The slider does not include a float field, but the IMGUI version does. I need to manually create my own composite control for this if I want to reuse it.
    • There should be a default button class that allows an icon to be set
    • All of the high-level controls should come with styling that matches the IMGUI controls from the legacy editor code
    This of course is only my humble opinion just having started out with the system, but I still want to share my experience in case the workflow can be improved or in case I can learn a few tricks from more experiences forum members. :)
     
  2. rastlin

    rastlin

    Joined:
    Jun 5, 2017
    Posts:
    94
    I would wager, that such observations are always present when transitioning to a framework with more robust foundations. You could have similar observations when transitioning to MVC from "legacy" approaches to web development, similarly moving from WinForms to WPF.

    The key think to have in mind is that ultimately UIElements is NOT supposed to speed up your simple workflows. You would always have an easier time just mashing things together with IMGUI for your simple use cases.

    The UIElements improves manageability and extensibility of UI controls and tremendously improves ability to follow the implementation flow/logic. That's the main benefit of such system. Dooring things "the right" way always takes more time then doing them fast.

    In time, controls will be improved, and users can write their own controls and share them simply with others through style files.
     
    Xarbrough likes this.
  3. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    424
    I think this is the main thing I'm hoping and waiting for, that we will be able to re-use prebuilt controls such as a slider and float field or a button with a simple icon etc.
     
  4. etienne_phil_unity

    etienne_phil_unity

    Unity Technologies

    Joined:
    Jan 15, 2019
    Posts:
    15
    It is true that for a reasonably simple editor, using UIElements is not necessarily faster than IMGUI (in terms of development time, because in terms of performance, UI Elements is faster than IMGUI) Actually putting together simple UI quickly is the forte of IMGUI. That being said, UIElements has a couple of advantages over IMGUI, it clearly separates styling, logic, etc.... This decoupling makes maintenance and code reuse simpler (as @rastlin mentioned). With IMGUI, state management quickly becomes tedious and error prone, as all UI code ends up altering a global state. When said state is not what you expect, tracking down the culprit can get hard fast. To simplify the use of UIElements, we're currently working on a visual editor. You can also use the UI Elements debugger. Thanks for your feedback :)
     
    Sylmerria and Xarbrough like this.
  5. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    390
    What you're saying makes sense ... UT just needs to make sure simple things don't become more and more complicated as Unity is made "better".

    I'm happy to learn the new system ... and the new SRP ... and so on. However, as one of our artists was mentioning to me, he really hopes Unity isn't going to become overly complicated (in reference to HDRP vs vanilla), which, when dealing with small companies that do well when people wear multiple hats ... becomes a barrier to having artists learn how to do minor tasks in non artist things, and so on.

    I guess my point is, improving Unity to scale upwards is great! However, if you can find a way to do that without raising the entry point for people who don't need that ... please put in the effort to do so. Especially when just about _everything_ in Unity is getting improved to scale upwards.

    I'd love to teach several of my co-workers how to write a custom editor to improve their workflows in simple ways without me ... but the more hoops there are to jump through, the less likely that becomes. I'm assuming we'll end up with UI Elements having simple default ways of creating what we already can create just as easily, while giving us the flexibility of doing more stuff, and in better ways.

    [Edit -- I'll mention that we tend to create 2d games that run on school computers ... so scaling upwards isn't really our biggest issue. Creating things easily and quickly is much more important, since Unity already handles whatever we're building just fine.]
     
    Last edited: Jul 15, 2019 at 5:37 PM
  6. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    273
    If the old system works for you (it does for me) then I don't see a reason to change as the new system is more complicated. It's defently better for super advanced stuff but I haven't needed that yet. Also the old system probably will be left for years so I don't believe that we'll have to convert our stuff to the new system anytime soon. Just look at some of the deprecated methods that have existed for MANY years....
     
  7. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    390
    Sure. I agree ... but Im still concerned that the overall direction of Unity doesnt grow more and more complicated. Im hoping we can still use this easily in 10 years for educational (not aaa 3d) games and not have to do things in more complicated ways than our use cases require, while looking back wistfully at the good old days ;).
     
    MCrafterzz likes this.
  8. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    390
    To be clear, all Im pointing out is we should hopefully wind up with an API that allows us to use uielements to build simple editors as quickly and easily as we currently can. Im all for having a more robust and faster system.
     
    MCrafterzz likes this.
  9. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    273
    Noone is against a faster system :p