Revision Difference
Guide_to_Widgets#551935
<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:⤶
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).⤶
⤶
## 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 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" />⤶
⤶
# 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 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 {⤶
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 {⤶
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⤶
⤶
TODO: <page>Creating Dock Windows</page>