Search Unity

[Editor Extension] Editor window designer

Discussion in 'Immediate Mode GUI (IMGUI)' started by AhrenM, Jul 1, 2015.

  1. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Hi all,

    This is to let everybody know about an extension/asset I'm building which is a first-gen WYSIWYG Designer for creating Unity editor windows. The current build supports labels, textfields, buttons, foldouts and toggles and can happily mix absolute positioning and layout modes. I'm working on some additional controls now and will probably have to add support for scrolling <shudder> before too long.

    The Designer follows a pretty standard Toolbox/Canvas/Properties layout for form editors. There is support for binding controls to back end data and the code gen spits out C# so you can focus on functional code.

    Ahren M

    [Edit: Unhelpful & unnecessary sas removed]
     
    Last edited: Sep 6, 2015
  2. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Still making good progress on this.

    Code gen is in and working a treat and scroll panels are also working if not exactly pixel perfect.
    I've passed the dog fooding stage which is to say the Designer is now quite capable of building it's own UI, which will hopefully speed up things.

    Still got quite a lot of clean up to do. The widget framework is getting close to a re-factor and the properties panel needs some attention.

    Screen grab this time. Here is the Designer open with it's own Toolbox window loaded.

    Capture.JPG
     
    mgear likes this.
  3. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Wow, well the properties window has just undergone a major overhaul.

    It was hand coded but brining it over the designer caused some scope creep. I'd only spec'd the property layout to handle basic properties and fields on a widget. Which for most widgets is fine, but the Properties window itself used compound widgets/custom controls.

    So the Designer wasn't able to edit these nested widgets, which fails the dog food test. Anywho, I spent time getting that working which had a flow on effect to the code gen, which also wasn't spec'd for nested content, so that is now also working.

    Here is a custom control in the Properties window. It's actually the control that handles editing of the "Form Name". The control contains two nested widgets, a label and a text field, which are now editable :)
    Capture1.JPG

    Right now I'm writing hooks that allow a widget property to have a custom editor in the Properties window. Properties of type Rect have a custom editor already, but this is hard coded in and I want to change this so they are defined by attributes assigned to the widget property. This should support lots of extensibility.

    Sadly while working on a long standing bug with the Boolean Property widget I've uncovered a mess with the binding system. So that's next up on the 'to fix' list.

    A.
     
  4. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Well that was easier than expected. Property editors can now be mapped in by attributes.
    For example this definition

    Code (CSharp):
    1. [UWidgetPropertyAttribute(CustomEditor = typeof(UEditorControlRect))]
    2. public RectOffset Padding
    3. {
    4.     get
    5.         {
    6.               if (_cachedPadding == null)
    7.                        _cachedPadding = new RectOffset();
    8.               return _cachedPadding;
    9.     }
    10. }
    Will drop the UEditorControlRect widget into the Properties window

    Capture3.JPG

    It's beer time.
     
  5. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Quick update.
    I've spent the last 4 days in a running battle with Unity serialization, just about through the worst of it. Who knew abstract base classes could cause such drama.

    The code base has suffered throughout this so, I've quite a bit of clean up to do, but I got to re-visit binding in the process and am much happier with how that is playing.

    I mentioned this over on the WIP thread and thought it worth dropping here. The GUI designer will be going in the asset store when complete, but the widget framework that runs the whole thing will be open source. The widget framework has no dependency on the Designer and it's pretty easy to layout simple forms through straight C#.

    This serialization stuff has knocked me back a bit, but I should have an engineering drop of that code next week.

    A.
     
  6. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,410
    Looking forward for this, i want to redo one editor plugin UI, its too painful to do from code only..

    unipix_ss1.jpg
     
  7. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    A bunch of updates on this.
    I've been working on a new toolbox for the designer. So far it looks a little something like this:
    Capture.JPG

    The toolbox (on the right) is populated by attributes on widget classes so if anyone creates their own custom widgets/controls the can be added to the designer with a tag like this:
    Code (CSharp):
    1. [UWidgetWidgetAttribute(eUWidgetDesignerCategory.Widgets, "Label")]
    2. public class UEditorWidgetLabel : UEditorWidgetTextBase
    3. {
    4. ...
    5. {
    Speaking of custom widgets, the first drop of the widget framework is on GitHub. This is still very much in an 'engineering phase' so things may change, but it kind of works. As per the dog food rule the new toolbox control was built in the Designer, so it's good enough for me. ;-)
    https://github.com/ahrenm/UEditorWidget

    As mentioned earlier the widget framework is independent of the GUI designer. Building a form through code will look rather familiar to anyone who has built GUI forms on C#/VB type platforms
    Code (CSharp):
    1. using System;
    2. using UnityEditor;
    3. using UnityEngine;
    4. using uAssist.UEditorWidgets;
    5.  
    6. public class TestWindow : frmBase
    7. {
    8.     //Menu option to open the window
    9.     [MenuItem("Window/Debug/Show Test Window")]
    10.     public static void OpenWindow()
    11.     {
    12.         //Open the window
    13.         EditorWindow.GetWindow<TestWindow>();
    14.     }
    15.  
    16.     //A random piece of data to be bound to a widget
    17.     public string LocalVarTitle = "Form Title";
    18.  
    19.     //Delcare the widgets to be used
    20.     public UEditorWidgetLabel _formTitle = UWidget.Create<UEditorWidgetLabel>();
    21.     public UEditorDecoratorHorizontalLine _line = UWidget.Create<UEditorDecoratorHorizontalLine>();
    22.     public UEditorPanelArea _panel = UWidget.Create<UEditorPanelArea>();
    23.     public UEditorWidgetLabel _userPrompt = UWidget.Create<UEditorWidgetLabel>();
    24.     public UEditorWidgetTextField _userInput = UWidget.Create<UEditorWidgetTextField>();
    25.     public UEditorWidgetButton _submit = UWidget.Create<UEditorWidgetButton>();
    26.  
    27.     //Public constructor
    28.     public TestWindow()
    29.     {
    30.         //Initalise the widgets
    31.         _formTitle.FontSize = 25;
    32.         _formTitle.FontStyle = FontStyle.BoldAndItalic;
    33.         _formTitle.Height = 25;
    34.         _formTitle.LayoutMode = ePositioningLayout.Layout;
    35.         _formTitle.Label = this.LocalVarTitle; //A once of write of this property
    36.         _panel.PositionX = 50;
    37.         _panel.PositionY = 50;
    38.         _panel.Height = 100;
    39.         _panel.Width = 300;
    40.         _panel.Border.FromInt(3, 3, 3, 3);
    41.         _panel.BaseStyle = "textField";
    42.         _userPrompt.LayoutMode = ePositioningLayout.Layout;
    43.         _userPrompt.Clipping = TextClipping.Clip;
    44.         _userPrompt.Label = "Enter a new form name:";
    45.         _userInput.LayoutMode = ePositioningLayout.Layout;
    46.         _userInput.Width = 200;
    47.         _userInput.Margin.FromInt(50, 4, 10, 2);
    48.         _submit.Label = "Update name";
    49.         _submit.LayoutMode = ePositioningLayout.Layout;
    50.         _submit.Width = 150;
    51.         _submit.Margin.FromInt(102, 4, 10, 2);
    52.  
    53.         //Build the component heriarchy
    54.         this.Components.Add(_formTitle);
    55.         this.Components.Add(_line);
    56.         this.Components.Add(_panel);
    57.         _panel.AddChild(_userPrompt);
    58.         _panel.AddChild(_userInput);
    59.         _panel.AddChild(_submit);
    60.  
    61.         //Bind the user input to my local var
    62.         _userInput.BindTo(this, "LocalVarTitle");
    63.  
    64.         //Optional: Uncomment below for real time updating
    65.         //_formTitle.BindTo(this, "LocalVarTitle");
    66.     }
    67.  
    68.     //We wire events here so they get re-created after seralization
    69.     public override void OnEnable()
    70.     {
    71.         //Generate an event handler for the button.OnClick
    72.         _submit.OnClick += _submit_OnClick;
    73.     }
    74.  
    75.     //Button OnClick handler
    76.     void _submit_OnClick(IUEditorWidgetClickable sender, EventArgs e)
    77.     {
    78.         //Update the widget label with the local var
    79.         _formTitle.Label = this.LocalVarTitle;
    80.     }
    81. }
    Which should look something like this:
    Capture1.JPG

    There are some useability odd and ends to clean up. But getting pretty close to a beta if anyone is game.

    A.

    P.S I've just noticed the _userPrompt label is getting clipped in the above example....meh...it's beer time.
     
    bigSky likes this.
  8. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Just a small update on this.
    I've been working on a Tab menu widget that works like the U5 Lighting window with multiple panels for display. It's working pretty well, with just a little tidy up on the properties page still to go.

    Capture.JPG

    I had to add a 'string list' capability as well to get this to work and this inspired me to move the custom code generation out of the Designer and into the open source framework. So now widget properties can be tagged with a Code Gen class as part of their attribute. This is about the final piece of extensibility I needed to really separate the two which is a good thing.

    I'll probably try and get a video out next week.

    A.
     
  9. NathanHold

    NathanHold

    Joined:
    Jul 17, 2013
    Posts:
    58
    Will the usage be like this:
    1. Create UI in your Unity Editor Designer plugin
    2. It generates a class
    3. Inherit from that class and do what we need
    4. Rebuild UI and regenerate as needed
    You said in the first post there is support for binding to backend data, what backends will be supported? Will it be easy to add new controls via inheritance and\or attributes?

    If this is all how it would work it would be perfect!
     
  10. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Pretty close to that.

    1. Generate a first iteration of the UI in the Designer.
    2. Execute a "Generate Code", this will create 2 code files as partials for the same class. A generated 'code behind' which contains all stuff necessary to runtime the form as specified and a 'code in-front' for writing user code, bindings, event handlers, logic and so forth.
    3. Subsequent build iterations update only the code behind file so user code is untouched.

    There is no need in the design to inherit from a created form. You certainly could if you had multiple forms that had the same layout. But I think if a derived class was loaded back into the Designer things would get a bit messy.

    Data Binding is similar to the binding process for Unity UI in that an object reference and member name are required. There is an example of this in the code 3 posts up on line 65 [Edit: and line 62 which is a live binding] which is commented out. Currently this has to be done through code, but I may look at wrapping UI around it.

    Widget are responsible for implementing their own binding. For example a widget designed to edit a Vector3 is going to expect to bound to a member of type Vector3. But most of that is hidden from the user. A Text Box widget for example will examine the type of a bound variable and do any conversions (int, float) that it know about.

    Absolutely new controls can be added. The widget framework is open source and should advance independently of the Designer. It's all attribute tags and everything from widgets, properties, property panel designers and code generation can be hooked in, without a Designer update. That said, creating new widgets/compound controls is non-trival right now and mostly hand coded. Extending the Designer to make this easier is on the todo list, right after Custom Inspector support.

    I've been away from this for week or so for personal reason, but should be cranking out some updates later this week.
     
  11. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    Trying madly to get this thing ready for some beta testing. Been tidying up a few loose ends and fixing the major usability concerns. The Toolbox and Hierarchy layout now sit side by side which is a lot easier to work with.

    As a proof of concept I threw together a form that allows a user to edit the name of GameObjects and toggle if Components are enable or disabled. The form is 100% operational and updates everything in real time.
    Capture.PNG

    You can see the layout above on the left. All the GUI code to make this work was built by the Designer. The only code I needed to write was the functional code below.


    Code (CSharp):
    1. //Store a reference to the currently selected button Widget
    2. private UEditorWidgetButton __activeGOButton;
    3.  
    4. //Build the hierarchy container on form startup
    5. public override void OnEnable()
    6. {
    7.     base.OnEnable();
    8.  
    9.     __goListContainer.ClearChilden();
    10.  
    11.     List<GameObject> __objectsInLevel = Object.FindObjectsOfType<GameObject>().ToList<GameObject>();
    12.  
    13.     foreach (var __go in __objectsInLevel)
    14.     {
    15.         //Create a new button widget
    16.         UEditorWidgetButton __newButton = UWidget.Create<UEditorWidgetButton>();
    17.         __newButton.BaseStyle = "label";
    18.         __newButton.Height = 16;
    19.         __newButton.Width = 190;
    20.         __newButton.LayoutMode = ePositioningLayout.Layout;
    21.         __newButton.Alignment = TextAnchor.MiddleLeft;
    22.         __newButton.BindTo(__go, "name");
    23.         __newButton.OnClick += __newButton_OnClick; //Bind to the OnClick event
    24.  
    25.         //Add the new widget to the form
    26.         __goListContainer.AddChild(__newButton);
    27.  
    28.     }
    29. }
    30.  
    31. private void __newButton_OnClick(IUEditorWidgetClickable sender, System.EventArgs e)
    32. {
    33.     if (sender == null)
    34.         return;
    35.  
    36.     //Update the selected GO apperance
    37.     if (this.__activeGOButton != null)
    38.     {
    39.         this.__activeGOButton.FontStyle = FontStyle.Normal;
    40.     }
    41.     this.__activeGOButton = (UEditorWidgetButton)sender;
    42.     this.__activeGOButton.FontStyle = FontStyle.Bold;
    43.            
    44.  
    45.     //Clear the keyboard focus
    46.     GUIUtility.keyboardControl = 0;
    47.            
    48.     //Bind the name property to the new GO
    49.     this._goNameProperty.BindTo(__activeGOButton.BoundObject, "name");
    50.  
    51.     //Update the components table
    52.     //===========================
    53.  
    54.     this._componentList.ClearChilden();
    55.  
    56.     //Retrieve the GameObject bound to the button Widget
    57.     GameObject __go = this.__activeGOButton.BoundObject as GameObject;
    58.  
    59.     List<Component> __components = __go.GetComponents<Component>().ToList<Component>();
    60.  
    61.     foreach (var __comp in __components)
    62.     {
    63.         if (__comp.GetType().IsSubclassOf(typeof(Behaviour)))
    64.         {
    65.             //Build a Bool Property control
    66.             Behaviour __behaviour = (Behaviour)__comp;
    67.             UEditorControlBool __newControl = UWidget.Create<UEditorControlBool>();
    68.             __newControl.LayoutMode = ePositioningLayout.Layout;
    69.             __newControl.BindTo(__behaviour, "enabled"); //Bind the property control to the 'enabled' member of the Behaviour
    70.             __newControl.PropertyLabel.Label = __comp.GetType().Name;
    71.             __newControl.PropertyLabel.Width = 170;
    72.  
    73.             //Add the new control to the form
    74.             this._componentList.AddChild(__newControl);
    75.         }
    76.     }
    77. }

    This is the kind of thing I really wanted to achieve. Easy to understand functional code and everything else hidden or abstracted out.

    The real limiting factor right now is the Widget framework itself which only supports a subset of the total controls the Unity Editor offers. Need to get to work building widgets for the more complex control types.

    If anyone wants to help beta this, let me know.

    A
     
  12. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    A quick update on this.
    I've sent a release candidate of the widget framework through to GitHub
    https://github.com/ahrenm/UEditorWidget

    It's got a bunch of changes, but the big one that nobody (other than me) will really care about is the garbage collector. I was having mad issues with ScriptableObjects leaking in large numbers during AppDomain reloads. And it was exponential so each reload would double the amount of leaked objects that were still getting phrased by the Unity Seralizer.

    The garbage collector now sorts that out and keeps things nice and trim.

    Still looking for beta testers if anybody is interested.

    A.
     
    graphicer822 likes this.
  13. BigBite

    BigBite

    Joined:
    Feb 20, 2013
    Posts:
    108
    Are you still working on this?
     
  14. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74

    Attached Files:

  15. BigBite

    BigBite

    Joined:
    Feb 20, 2013
    Posts:
    108
    Understood, I just found this a couple of days ago and it peeked my interest. Sad that you could not finish it, hopefully someone picks up the torch. Good luck with everything else!
     
  16. Delarn

    Delarn

    Joined:
    Jan 16, 2018
    Posts:
    31
    Sorry to ressurect this thread, but this is an awesome tool. I was looking for an asset like this one. It will make my editorwindow project a lot better and done faster. Thanks a lot for your work.
     
  17. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    289
    Sorry for promoting my asset here but this thread has been dead for 2 years. If you want a tool for the Editor UI you can check my own, I just released it Voltage - Editor UI. Hope it helps.
     
    Delarn likes this.
  18. AhrenM

    AhrenM

    Joined:
    Aug 30, 2014
    Posts:
    74
    No worries at all whileBreak. And congrats on getting Voltage out to market. I have a reasonable idea how tough that can be so well done. Out of random interest do voltage forms bake out to metadata files that are processed by your runtime or they standalone .cs files?

    I went standalone .cs with a widget framework, but ran into a nasty chicken and egg problem between the editor (closed source .dll) and the framework consisting of open source loose .cs files. It just turned into a compile time nightmare.

    Obviously you're not having that, so kudos ;-)
     
  19. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    289
    For the moment I don't have any window editor, is just the framework. So is the same as before, but extending VoltageWindow instead WindowEditor, same for editors. I just wanted to solve the layout nightmae that EditorScripting had
     
    AhrenM likes this.