This is a step-by-step guide on how to create a simple editor window that responds to user input using Slate, Unreal Engine's UI framework. This page does not provide a comprehensive guide on how Slate interacts with other systems. Rather, it focuses on the fundamentals of how Slate's syntax relates to the layout of your UI, and the basics of making your UI respond to events.
1. Project Setup
This guide uses a new project called SlateQuickstartGame with the following settings:
- Template: Third-Person Project
- C++ Project
You can also follow this guide using an existing project. This project does not depend on any specific target platform, as it focuses on editor functionality.
2. Creating a Window Plugin
To get started on your new editor window quickly, open the Edit > Plugins window, then click the + Add button to create a new plugin. Choose Editor Standalone Window as the plugin type, then call it SlateQuickstartWindow. Click Create Plugin to finish.
Click to enlarge image.
This automatically creates the Plugin module with the necessary C++ classes to support a basic editor window. Close the editor and recompile your project, then open the project again. You can find your new editor window under the Window dropdown.
Click to enlarge image.
When you initially view this window, it will display placeholder text but no interactive elements.
Understanding the Window's Code
Before continuing to build your menu, you should review the code in SlateQuickstartWindowModule.cpp for a quick preview of how to use Slate's declarative syntax. The Slate code is contained in the OnSpawnPluginTab function. When you initially view it, the function reads as follows:
TSharedRef<SDockTab> FSlateQuickstartWindowModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
FText WidgetText = FText::Format(
LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
FText::FromString(TEXT("FSlateQuickstartWindowModule::OnSpawnPluginTab")),
FText::FromString(TEXT("SlateQuickstartWindow.cpp"))
);
return SNew(SDockTab)
.TabRole(ETabRole::NomadTab)
[
// Put your tab content here!
SNew(SBox)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(WidgetText)
]
];
}
This function performs the following steps:
- Builds a string called
WidgetTextusingFText::Formatto combine several strings. - Defines a new
SDockTabusing the SNew function. This represents the dockable window. - Fills the
SDockTabwith anSBoxelement which can contain content. This box hasHAlignandVAlignparameters that align it to the center of the window. - Adds a new
STextBlockto theSBoxand sets its text to the value ofWidgetText. - Returns the
SDockTabcontaining all these UI elements.
The best way to understand how this works is to think of how you would create a similar user interface in UMG. To re-create this in UMG, you would drag a box widget onto the stage as the root UI element, then place a Text widget inside of it and change its alignment to center. The stage and hierarchy would resemble the following image.
Click to enlarge image.
In the case of the SlateQuickstartWindow, the viewport stage is replaced with the SDockTab widget. When you review the code, the square brackets define what elements are nested inside a given widget, while periods set the value of the parameters you would see in the Details panel.
For more information about Slate's declarative syntax, refer to the Slate Overview.
3. Creating a Slate Widget For the Menu
You can add Slate widgets directly to the FSlateQuickstartWindowModule class generated by the Plugin wizard. However, there are some limitations to how you can handle your widgets' callbacks if you do this. Therefore, you should create a dedicated Slate widget to hold the menu's contents, then add that widget to the FSlateQuickstartWindowModule class.
-
In the Content Drawer, click the C++ Classes folder, then right-click and click New C++ Class.
Click to enlarge image.
-
In the Add C++ Class window, select Slate Widget, then click Next.
-
Change your new C++ class's settings as follows:
Click to enlarge image.
-
Class Type: Public
-
Name: QuickStartWindowMenu
-
Module: SlateQuickstartWindow (Editor)
Click Create Class to finish.
-
Make sure you set the Module drop-down to the SlateQuickstartWindow module before you click Create Class. This adds the class to your plugin's code. If you don't do this, the wizard will put it in your game's code, and your Slate widget will not be visible to the plugin.
The wizard creates a new C++ class called SQuickStartWindowMenu in your plugin's Source folder, then regenerates your project's Visual Studio solution.
4. Populating Your Menu With Widgets
To add Slate widgets to your menu, open SQuickStartWindowMenu.cpp and add them to the Construct function. When you initially view this function, it reads as follows:
void SQuickStartWindowMenu::Construct(const FArguments& InArgs)
{
/*
ChildSlot
[
// Populate the widget
];
*/
}
Follow these steps to populate the window:
-
Delete the commented-out code inside the
Constructfunction. -
Inside the
Constructfunction, add a new SVerticalBox with the SNew function. Follow that with two+SVerticalBox::Slot()elements. Give both slots the.AutoHeight()property.SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ ] +SVerticalBox::Slot() .AutoHeight() [ ] -
Inside the first Vertical Box slot, add a
SHorizontalBoxwith two slots. Give both slots the.VAlign(VAlign_Top)property.+SVerticalBox::Slot() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ ] +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ ] ]This will nest the horizontal box inside the vertical box, effectively dividing this first slot into two columns.
-
In the first horizontal slot, add a new
STextBlockwidget. Set the.TexttoFText::FromString("Test Button").+SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(STextBlock) .Text(FText::FromString("Test Button")) ]Whenever you display user-facing text, you should use
FTextinstead ofFStringor other methods of displaying text, as it provides support for localization. For more information, refer to Text Localization. -
In the second horizontal slot, add a new
SButtonwidget. Set its.TexttoFText::FromString("Press Me").+SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(SButton) .Text(FText::FromString("Press Me")) ] -
In the second Vertical Box slot, create another Horizontal Box with two slots. Give the first horizontal slot a
STextBlockwidget that reads"Test Checkbox", then add aSCheckBoxwidget to the second horizontal slot.+SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(STextBlock) .Text(FText::FromString("Test Checkbox")) ] +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(SCheckBox) ] ] -
The full definition of your Slate window should now read as follows:
return SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(STextBlock) .Text(FText::FromString("Test Button")) ] +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(SButton) .Text(FText::FromString("Press Me")) ] ] +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(STextBlock) .Text(FText::FromString("Test Checkbox")) ] + SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(SCheckBox) ] ] ]; -
Save and compile your code, then open your project in Unreal Editor and open your window.
You can't compile Slate windows while Live Coding is active and your project is open in Unreal Editor. Make sure to close Unreal Editor before trying to compile your code.
When you open your window, it should look like this:
Neither the button or the checkbox do anything yet, but you will add functionality to them in the next section.
5. Binding Events to Interactive Widgets
Interactive Slate widgets use events to respond to interactions like clicking or toggling. In this section, you will create several delegates to drive the interaction for the checkbox and button from the previous section.
-
Open
SQuickStartWindowMenu.hand declare the following functions and variables:public: FReply OnTestButtonClicked(); void OnTestCheckboxStateChanged(ECheckBoxState NewState); ECheckBoxState IsTestBoxChecked() const; protected: bool bIsTestBoxChecked;The functions will contain the logic that runs when you click the test button and test checkbox. The checkbox will use the
IsTestBoxCheckedfunction to determine what state it should display, andbIsTestBoxCheckedwill cache that state. -
In
SQuickStartWindowMenu.cpp, provide the following implementations for the checkbox's functions:void SQuickStartWindowMenu::OnTestCheckboxStateChanged(ECheckBoxState NewState) { bIsTestBoxChecked = NewState == ECheckBoxState::Checked ? true : false; } ECheckBoxState SQuickStartWindowMenu::IsTestBoxChecked() const { return bIsTestBoxChecked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }These provide a getter and setter for the checkbox's current state. The
OnTestCheckboxStateChangedfunction will set the value ofbIsTestBoxCheckeddepending on what the user changes the checkbox's state to. Meanwhile, theIsTestBoxCheckedfunction will read that variable and translate it back into anECheckBoxState. -
Add the
.OnCheckStateChangedparameter to theSCheckBoxwidget, and provide a reference to theFSlateQuickstartModule::OnTestCheckboxStateChangedfunction. Then, add theIsCheckedparameter, and provide a reference to theSQuickStartWindowMenu::IsTestBoxCheckedfunction.+SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(SCheckBox) .OnCheckStateChanged(FOnCheckStateChanged::CreateSP(this, &SQuickStartWindowMenu::OnTestCheckboxStateChanged)) .IsChecked(FIsChecked::CreateSP(this, &SQuickStartWindowMenu::IsTestBoxChecked)) ]You now have a mechanism to track when the user checks and unchecks the
SCheckBoxwidget. -
Provide the following implementation for the
OnTestButtonClickedfunction:FReply SQuickStartWindowMenu::OnTestButtonClicked() { UE_LOG(LogTemp, Warning, TEXT("Hello, world! The checkbox is %s."), (bIsTestBoxChecked ? TEXT("checked") : TEXT("unchecked"))); return FReply::Handled(); }This function outputs a short message to the output log stating whether the checkbox is checked.
-
Add the
.OnClickedparameter to theSButton, then provide a reference to theSQuickStartWindowMenu::OnTestButtonClickedfunction.+SHorizontalBox::Slot() .VAlign(VAlign_Top) [ SNew(SButton) .Text(FText::FromString("Press Me")) .OnClicked(FOnClicked::CreateSP(this, &SQuickStartWindowMenu::OnTestButtonClicked)) ]
6. Adding Your Menu to the Window
Finally, you need to add the Slate widget you created to the menu tab's code.
-
Open
SlateQuickstartWindowModule.cppand delete the contents of the SDockTab. Its code should now read as follows:return SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ ]; -
Add your
SQuickStartWindowMenuwidget inside theSDockTab.return SNew(SDockTab) .TabRole(ETabRole::NomadTab) [ SNew(SQuickStartWindowMenu) ];
Save and compile your code, then open Unreal Editor and test your window.
Final Result
When you click the test button, your console log should print the message "Hello, world! The checkbox is unchecked." If you check the check box, it should print the message "Hello, world! The checkbox is checked." This provides a basic working example of a Slate window which you can expand on to create custom editor windows.
On Your Own
For examples of different Slate widgets and the many ways they can bind delegates, refer to STestSuite.cpp. You can find this in your engine's install directory under Source\Runtime\AppFramework\Private\Widgets\Testing. You can experiment with these widgets to create different layouts and interactive elements. You can also experiment with using Slate UI elements to interact with assets or data in your project, or the World in the level editor.