Revision Difference
Guide_to_Widgets#551941
<cat>Code.Editor</cat>
<title>Guide to Widgets</title>
# A beginners guide to Widgets
Editor UIs are built entirely out of widgets. Widgets are different from [ASP.NET Razor](ui-razor), which is used for the in-game UI. Widgets are available in the Editor namespace, thus you should only make them from a [tool or editor project](Creating_Editor_Tools).
Widgets can be various elements or components, such as buttons, text boxes, trees, or images.
Children widgets are the smaller widgets that are contained within a larger widget, known as the parent widget. Imagine a parent widget as a container, like a box, and the children widgets are like smaller objects placed inside that box.
If a widget does not have a parent, it is a root widget. This widget will act as a window on the user's operating system.
# Creating and opening a window
There are three primary ways to do so:
There are four primary ways to do so:
1. Using the [Dock attribute](https://asset.party/api/Editor.DockAttribute) on a class that inherits [Widget](https://asset.party/api/Editor.Widget) at some level.
2. Implementing the [IAssetEditor interface](https://asset.party/api/Editor.IAssetEditor) and opening an asset with the extension specified on the class' [EditorForAssetType attribute](https://asset.party/api/Editor.EditorForAssetTypeAttribute).
3. Manually creating the widget with the Parent field set to `null`, typically in a static method with the [Menu attribute](https://asset.party/api/Editor.MenuAttribute).
4. Using the [Tool attribute](h) on a class that inherits [Widget](https://asset.party/api/Editor.Widget) at some level.⤶
## Option 1 - Widget with Dock attribute
This will create widgets that behave like the Asset Browser, Projects list, Console feed, etc.
```cs
using Editor;
namespace MyToolProject; //never forget your namespace
[Dock("Editor", "Example Editor Dock", "local_fire_department")]
public partial class DockAttrExample : Widget {
public class DockAttrExample : Widget {
public DockAttrExample(Widget parent) : base(parent, false) {
//the constructor's parameters must have a widget and nothing else
//otherwise the attribute will fail to find a constructor to use when creating the widget
//create your child widgets
}
}
```
The Dock attribute has three parameters
1. `target`
This is the [DockWindow](https://asset.party/api/Editor.DockWindow) registered with the attribute that the widget should show up in.
`Editor` and `Hammer` are the only known options.
2. `name`
What should be displayed when listing available docks.
3. `icon` (optional)
Identifier of the icon to use.
The dock can then be opened from the associated window's list.
For `Editor`:
<upload src="8f39a/8dbc7a4c911f556.png" size="15179" name="sbox-dev_gYojMiKVH2.png" />
For `Hammer`:
<upload src="8f39a/8dbc7a4f928920d.png" size="34001" name="sbox-dev_glPOyG8aAl.png" />
## Option 2 - Asset Editor
```cs
using Editor;
namespace MyToolProject; //never forget your namespace
[EditorForAssetType("8letters")] //cannot be more than 8 characters, will need to be a registered asset
public class AssetEditorExample : Widget, IAssetEditor {
//return false if you want to have a widget created for each asset
//return true if you want only one widget to be made
public bool CanOpenMultipleAssets => true;
public AssetEditorExample(Widget parent, bool isDarkWindow = false) : base(parent, isDarkWindow) {
//create your child widgets
}
public void AssetOpen(Asset asset) {
//called every time an asset is opened in the asset browser (only for the type specified in EditorForAssetType)
//do something with the asset, like call LoadResource
//save your asset by calling SaveToDisk on the asset with the resource
//if CanOpenMultipleAssets returns true,
//you should refocus this widget
Focus();
}
}
```
The [EditorForAssetType attribute](Editor.EditorForAssetTypeAttribute) should have the extension of your [custom asset type](Custom_Asset_Types).
When the an asset is double clicked in the Asset Browser (or the edit button on the inspector) the widget will be created and `AssetOpen` will be called with the asset.
You may need to load
## Option 3 - Manually
Not recommended for most use cases, but still an option.
```cs
using Editor;
namespace MyToolProject; //never forget your namespace
public class WidgetExample : Widget {
public WidgetExample(Widget parent, bool isDarkWindow = false) : base(parent, isDarkWindow) {
//create children widgets
//if you don't call this, you essentiall create a memory leak
Show();
}
//methods
[Menu("Editor", "Example Code/Widget Example")]
public static void OpenExample() {
WidgetExample widget = new(null);
}
}
```
If the constructor doesn't call `Show`, then you will need to call it somewhere after constructing.
This only needs to be done for widgets that don't have a parent.
The static method with the [Menu attribute](https://asset.party/api/Editor.MenuAttribute) creates an entry in the Editor's tool bar.
<upload src="8f39a/8dbc7aa50929051.png" size="13224" name="sbox-dev_drdnpJjEiz.png" />
⤶
## Option 4 - Widget with Tool attribute⤶
⤶
Does not have to be a DockWindow, just has to inherit Widget. This is typically used for larger tools with many features and widgets.⤶
⤶
```cs⤶
using Editor;⤶
⤶
namespace MyToolProject; //never forget your namespace⤶
⤶
[Tool("Full Scale Tool", "local_fire_department", "Example of a large tool with lots of widgets")]⤶
public class ToolAttrExample : DockWindow {⤶
public ToolAttrExample() : base() {⤶
//you must have an anonymous constructor⤶
//otherwise the attribute will fail to find a constructor to use when creating the widget⤶
⤶
//create your child widgets⤶
}⤶
}⤶
```⤶
# Example Window
Let's create a widget that tests our user's *Half-Life: 2* knowledge.
```cs
using Editor;
using System;
using System.Collections.Generic;
namespace MyToolProject; //never forget your namespace, choose a more appropriate one than this though
public class HalfLife2Exam: Widget {
public class HalfLife2Exam : Widget {
public HalfLife2Exam(Widget parent, bool isDarkWindow = false) : base(parent, isDarkWindow) {
//set to false if you want the widget to be hidden instead
//only do this if you will re-open the widget instead of creating a new one
DeleteOnClose = true;
//these sizes will get scaled by the operating system's window scaling
MinimumSize = new(256, 256);
Size = new(384, 512);
//Name is used for the widget debugger, and DockWindow widgets
//it is optional
Name = "HL2Exam";
//also optional, but highly recommended if you are making your widget as a window
WindowTitle = "Exam - Half-Life: 2";
//also optional, but looks much better than the operating system's default
SetWindowIcon("edit_note");
//a Layout controls the positioning and sizing of widgets added to it
//it's not necessary, but makes things a LOT easier
Layout = Layout.Column();
//we will write more here
//leave this at the bottom
Show();
}
[Menu("Editor", "Exams/Half-Life: 2")]
public static void OpenExam() {
HalfLife2Exam _ = new(null);
}
}
```
Using the Editor's tool bar we can open the window
<upload src="8f39a/8dbc7ac1f241e31.png" size="3288" name="sbox-dev_xWOFyc4Q9y.png" />
Let's start adding a few widgets to ask questions and take answers. First question we will ask, is for the name of the character you play as.
```cs
Layout.Add(new Label("1. What is the name of the character you play as?", this));
//single line text input
GordonsName = Layout.Add(new LineEdit(this) { PlaceholderText = "Firstname Lastname" });
```
If we re-open our exam, we can now see the label and text input.
<upload src="8f39a/8dbc7acf0f7988f.png" size="8425" name="sbox-dev_YHrZshWzTI.png" />
It's nice to see things working, but the spacing is ugly. We can fix this by adding a stretch cell to occupy the extra space in one spot.
```cs
Layout.AddStretchCell();
```
This will bunch the label and text input together
<upload src="8f39a/8dbc7ad512e8ad5.png" size="8459" name="sbox-dev_VIOWf8ouPo.png" />
Changing when the stretch cell is added to the layout, you can spread the widgets out differently.
This is what happens when you add the stretch cell after the label, but before the text input
<upload src="8f39a/8dbc7ad893bad5d.png" size="8458" name="sbox-dev_jzu3bGhmc6.png" />
For now, let's make the stretch cell as the last member of the layout.
Next, let's ask what weapons the player can use in Half-Life 2 using a list of check boxes
```cs
//code ...
public class HalfLife2Exam: Widget {
public class HalfLife2Exam : Widget {
private LineEdit GordonsName;
private List<CheckBox> CorrectCheckBoxes = new();
private List<CheckBox> WrongCheckBoxes = new();
public HalfLife2Exam(Widget parent, bool isDarkWindow = false) : base(parent, isDarkWindow) {
//code ...
Layout.Add(new Label("2. Which of these weapons can players use in Half-Life: 2?", this));
//create the check boxes and put them into a list of correct and incorrect
//we will evaluate these later
CorrectCheckBoxes.Add(Layout.Add(new CheckBox("SMG", this)));
CorrectCheckBoxes.Add(Layout.Add(new CheckBox("Crowbar", this)));
WrongCheckBoxes.Add(Layout.Add(new CheckBox("AK-47", this)));
WrongCheckBoxes.Add(Layout.Add(new CheckBox("M4A1", this)));
CorrectCheckBoxes.Add(Layout.Add(new CheckBox("AR2", this)));
//fill up space after all our widgets
Layout.AddStretchCell();
//leave this at the bottom
Show();
}
//code ...
}
```
Notice we store the check boxes into a list. We will need to access to the check boxes later when grading the test.
The check boxes work, but there should be spacing between question 1 and 2. We can solve this using the layout
```cs
Layout.AddSpacingCell(16);
```
Add this between the text input and the question 2 label, and a 16 pixel (scaled by operating system) gap will now appear between the two questions
<upload src="8f39a/8dbc7b078b8e6fb.png" size="5886" name="sbox-dev_NazSyZz9hL.png" />
This test is already quite big with 2 whole questions, so let's wrap it up by adding a submit button
```cs
Layout.AddSpacingCell(16);
Layout.Add(new Button("Submit", this) { Clicked = ExamSubmitted });
```
and a method to grade the exam when it's clicked
```cs
public void ExamSubmitted() {
/* grade exam however you wamt */
float score = 75f;
PopupWindow popup = new("Exam results", $"You scored {score}%");
popup.Show();
}
```
The final code should look something like
```cs
using Editor;
using System;
using System.Collections.Generic;
namespace MyToolProject; //never forget your namespace, choose a more appropriate one than this though
public class HalfLife2Exam: Widget {
public class HalfLife2Exam : Widget {
private LineEdit GordonsName;
private List<CheckBox> CorrectCheckBoxes = new();
private List<CheckBox> WrongCheckBoxes = new();
public HalfLife2Exam(Widget parent, bool isDarkWindow = false) : base(parent, isDarkWindow) {
//set to false if you want the widget to be hidden instead
//only do this if you will re-open the widget instead of creating a new one
DeleteOnClose = true;
//these sizes will get scaled by the operating system's window scaling
MinimumSize = new(256, 256);
Size = new(384, 512);
//Name is used for the widget debugger, and DockWindow widgets
//it is optional
Name = "HL2Exam";
//also optional, but highly recommended if you are making your widget as a window
WindowTitle = "Exam - Half-Life: 2";
//also optional, but looks much better than the operating system's default
SetWindowIcon("edit_note");
//a Layout controls the positioning and sizing of widgets added to it
//it's not necessary, but makes things a LOT easier
Layout = Layout.Column();
Layout.Add(new Label("1. What is the name of the character you play as?", this));
//single line text input
GordonsName = Layout.Add(new LineEdit(this) { PlaceholderText = "Firstname Lastname" });
Layout.AddSpacingCell(16);
Layout.Add(new Label("2. Which of these weapons can players use in Half-Life: 2?", this));
//create the check boxes and put them into a list of correct and incorrect
//we will evaluate these later
CorrectCheckBoxes.Add(Layout.Add(new CheckBox("SMG", this)));
CorrectCheckBoxes.Add(Layout.Add(new CheckBox("Crowbar", this)));
WrongCheckBoxes.Add(Layout.Add(new CheckBox("AK-47", this)));
WrongCheckBoxes.Add(Layout.Add(new CheckBox("M4A1", this)));
CorrectCheckBoxes.Add(Layout.Add(new CheckBox("AR2", this)));
Layout.AddSpacingCell(16);
Layout.Add(new Button("Submit", this) { Clicked = ExamSubmitted });
//fill up space after all our widgets
Layout.AddStretchCell();
//leave this at the bottom
Show();
}
public void ExamSubmitted() {
int points = 0;
int maximumPoints = 1;
//remove capitalization, and trim whitespace
if (GordonsName.Value.ToLower().Trim() == "gordon freeman")
points++;
foreach (CheckBox checkbox in CorrectCheckBoxes) {
maximumPoints++;
if (checkbox.Value)
points++;
}
foreach (CheckBox checkbox in WrongCheckBoxes) {
maximumPoints++;
if (!checkbox.Value)
points++;
}
float score = MathF.Round(points / (float) maximumPoints * 100f);
PopupWindow popup = new("Exam results", $"You scored {score}%");
popup.Show();
}
[Menu("Editor", "Exams/Half-Life: 2")]
public static void OpenExam() {
HalfLife2Exam _ = new(null);
}
}
```
Fill out the exam
<upload src="8f39a/8dbc7b4cdbb6a51.png" size="14180" name="sbox-dev_DHmeBe6p4W.png" />
and click submit
<upload src="8f39a/8dbc7b4e04bff89.png" size="4927" name="sbox-dev_pbrvvCMbFM.png" />
# Dock Windows
Read <page>Widget Docking</page> for guidance