Create an Item Editor with UI Elements without uss and uxml in Unity
Tags: unity3d, uielements, csharp, gamedev
Published: 2021-02-27 13:22:55

##  So you wanna do some editor scripting?

I've been following UI Elements since its preview release. Often, I tried from their
[examples repo](https://github.com/Unity-Technologies/UIElementsExamples) but couldn't dig in so much and this time I'll make a little editor tool walkthrough.

##  UI Elements

UI Elements is the new kid in town for Unity developers. I give it a go their Recently released some official beginner resources [here](https://learn.unity.com/tutorial/uielements-first-steps) but I didn't like that `uss` and `uxml` stuff. Since I kinda wanted 'database-like editor tool' to hack and learn this, I'll stick with C# Api this time.

Here is the final product.

![final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ck197rbt8zy53hzwvwgn.png)

If you wanna move along, grab the code [here](https://github.com/cemuka/UIElementsItemEditor).

1. Editor window
2. Layout with styles
3. Add a scroll view
4. Add fields
5. Save to json

### 1. Editor window

This part is familiar, create a folder `Editor` make a script, I'll say `ItemEditor` and hook up your editor window with `MenuItem` attribute. 

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class ItemEditor : EditorWindow
{
    [MenuItem("Tools/Item Editor")]
    public static void OpenWindow()
    {
        var window = GetWindow<ItemEditor>();
        window.titleContent = new GUIContent("Item Editor");
    }
}
```

Nothing fancy, new menu will pop up in the editor you'll get a fresh empty window.
Next, draw some boxes.

### 2. Layout with styles

`OnEnable` call is entry point for my little editor. I'll draw a box for listing items, and another to hold fields for adding a new item.

```csharp
private void OnEnable()
{
    var root = this.rootVisualElement;
    
    var itemsListBox = new Box();
    var newItemBox = new Box();

    root.Add(itemsListBox);
    root.Add(newItemBox);
}
```

Well, if you look your window again, you won't see anything. So this is where layout involves.
I'll divide my window as 1 portion for list, 3 portion for new item section.

```csharp
private void OnEnable()
{
    var root = this.rootVisualElement;
    root.style.flexDirection = FlexDirection.Row;
    
    var itemsListBox = new Box();
    itemsListBox.style.flexGrow = 1f;
    itemsListBox.style.flexShrink = 0f;
    itemsListBox.style.flexBasis = 0f;
    itemsListBox.style.flexDirection = FlexDirection.Column;

    var newItemBox = new Box();
    newItemBox.style.flexGrow = 3f;
    newItemBox.style.flexShrink = 0f;
    newItemBox.style.flexBasis = 0f;
    
    root.Add(itemsListBox);
    root.Add(newItemBox);
}
```

First of all, if you haven't heard about flex css like me before, 
it may be a good idea to look at how it works, since it uses a flex engine under the hood.

Now I can see my boxes.

![sample 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1uklphby9ofvd9nx6k1w.png)

https://docs.unity3d.com/Manual/UIE-LayoutEngine.html

And this is the resource that helped me to understand flex.

https://css-tricks.com/snippets/css/a-guide-to-flexbox/

Yeah, mo more `StartHorizontal` or other IMGUI method for layout your window. You won't need `OnGUI` at all.

### 3. Add a scroll view

It's so simple, like adding a box I did above.

```csharp
private ScrollView _scrollView;

private void SetupItemList(Box parent)
{
    _scrollView = new ScrollView();
    _scrollView.showHorizontal = false;
    _scrollView.style.flexGrow = 1f;
    parent.Add(_scrollView);
}
```

Also, a label fits good.

```csharp
private void SetupItemList(Box parent)
{
    var listLabel = new Label("Item List");
    listLabel.style.alignSelf = Align.Center;
    listLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
    parent.Add(listLabel);

    _scrollView = new ScrollView();
    _scrollView.showHorizontal = false;
    _scrollView.style.flexGrow = 1f;
    parent.Add(_scrollView);
}
```

![sample 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr8zj8jdoqoseln4pstk.png)

### 4. Add fields

Let's populate other box with some fields. 
Name, rarity and price is simple enough for my little tool.

```csharp
private void SetupFields(Box parent)
{
    var newItemLabel = new Label("New Item");
    newItemLabel.style.alignSelf = Align.Center;
    newItemLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
    parent.Add(newItemLabel);

    var nameField = new TextField("Name: ");
    var rarityField = new EnumField("Rarity: ", ItemRarity.Common);
    var priceField = new IntegerField("Price: ");
    parent.Add(nameField);
    parent.Add(rarityField);
    parent.Add(priceField);
    
    var saveItemButton = new Button();
    saveItemButton.text = "Save Item";
    parent.Add(saveItemButton);
}
```

Now, I'll create a method for adding items to my scroll view list and when I click
to save item button, it'll get values from fields.

```csharp
private void CreateListItem(string name)
{
    var itemElement = new VisualElement();
    itemElement.style.flexDirection = FlexDirection.Row;
    itemElement.focusable = true;
    itemElement.name = name;

    var remove = new Button();
    remove.text = "-";
    itemElement.Add(remove);

    var nameButton = new Button();
    nameButton.text = name;
    nameButton.style.flexGrow = 1f;
    itemElement.Add(nameButton);

    _scrollView.contentContainer.Add(itemElement);
}
```

I create an empty visual element and afterwards, I add it to scroll view as a whole.
Pretty clean looking and easy comparing between `IMGUI` api.

Next, I'll create a class `ItemData`.

```csharp
[System.Serializable]
public class ItemData
{
    public int id;
    public string name;
    public ItemRarity rarity;
    public int price;
}
```

Now I can save my item when I hit to save button.

```csharp
private List<ItemData> _savedItems = new List<ItemData>();

private void SetupFields(Box parent)
{
    var newItemLabel = new Label("New Item");
    newItemLabel.style.alignSelf = Align.Center;
    newItemLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
    parent.Add(newItemLabel);

    var nameField = new TextField("Name: ");
    var rarityField = new EnumField("Rarity: ", ItemRarity.Common);
    var priceField = new IntegerField("Price: ");
    parent.Add(nameField);
    parent.Add(rarityField);
    parent.Add(priceField);
    
    var saveItemButton = new Button();
    saveItemButton.text = "Save Item";
    saveItemButton.clicked += () => {
        if (string.IsNullOrWhiteSpace(nameField.value) == false)
        {
            var item = new ItemData();
            item.id = new Guid().GetHashCode();
            item.name = nameField.value;
            item.rarity = (ItemRarity)rarityField.value;
            item.price = priceField.value;

            _savedItems.Add(item);

            //  set default values to clear fields
            nameField.value = "";
            rarityField.value = ItemRarity.Common;
            priceField.value = 0;

            CreateListItem(item.name);
        }
    };

    parent.Add(saveItemButton);
}
```

And, I'll add a couple test items. It works!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3udwp3zjlak2myzx060u.png)

### 5. Save to json

Finally I should be able to save and load my data. I'll save my items as a json file in `Resources` folder. 

To handle my json data, I'll create a class and `JsonUtility` will do the magic.

```csharp
public class ItemsFile
{
    public List<ItemData> data;
}
```

```csharp
public void LoadData()
{
    var path = Application.dataPath + "/Resources/items.json";

    if (System.IO.File.Exists(path))
    {
        var file = System.IO.File.ReadAllText(path);
        _savedItems = JsonUtility.FromJson<ItemsFile>(file).data;
        
        foreach (var item in _savedItems)
        {
            CreateListItem(item.name);
        }
    }
}
```

```csharp
public void SaveData()
{
    var path = Application.dataPath + "/Resources/items.json";

    var itemsFile = new ItemsFile();
    itemsFile.data = new List<ItemData>(_savedItems);
    var itemsFileAsJson = JsonUtility.ToJson(itemsFile);
    System.IO.File.WriteAllText(path, itemsFileAsJson);
}
```

Just load when window opens and save when you hit save button.
Now I have a working sharp looking item editor tool that save data as json.

As a final touch, I'll add removing an item from my list.

```csharp
private void CreateListItem(ItemData itemData)
{
    var itemElement = new VisualElement();
    itemElement.style.flexDirection = FlexDirection.Row;
    itemElement.focusable = true;

    var remove = new Button();
    remove.text = "-";
    remove.clicked += () => {
        _scrollView.contentContainer.Remove(itemElement);
        _savedItems.Remove(itemData);
        SaveData();
    };
    itemElement.Add(remove);

    var nameButton = new Button();
    nameButton.text = itemData.name;
    nameButton.style.flexGrow = 1f;
    itemElement.Add(nameButton);

    _scrollView.contentContainer.Add(itemElement);
}
```

![sample 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5hjf3m501gdwhktapi9.png)

That's all for now. 
UI Elements proved me that it's better aproach than 
legacy editor scripting and I'll enjoyed without uss and uxml so far :)

If you missed above, grab the code [here](https://github.com/cemuka/UIElementsItemEditor).

Do you like styling and this flex styling in unity? Let me know what do you think.

Thanks for reading!