SkillAgentSearch skills...

EmmsUI

Unreal plugin for immediate mode UMG UI, for use with UnrealEngine-Angelscript

Install / Use

/learn @Hazelight/EmmsUI
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

EmmsUI Plugin for UnrealEngine-Angelscript

EmmsUI is a plugin for drawing immediate mode UI in Unreal, built for the UnrealEngine-Angelscript fork. It works by maintaining a tree of UMG widgets that is controlled through angelscript code. Because UnrealEngine-Angelscript provides runtime hotreloading of script files, this makes it very easy to develop editor and tool UI.

EmmsUI functions live in the mm:: namespace to be easy to type, hence the name.

As an example, the angelscript code:

mm::WithinBorder(FLinearColor::Black);
mm::BeginHorizontalBox();

	mm::VAlign_Center();
	mm::Image(n"ClassIcon.Actor");

	mm::VAlign_Center();
	mm::Padding(10, 0);
	mm::Text(f"{Actor.ActorNameOrLabel}");

	if (mm::Button("Destroy Actor"))
		Actor.DestroyActor();

mm::EndHorizontalBox();

Results in the UI:

Image of the example UI generated by the above code

Why based on UMG?

Many existing plugins for immediate mode UI exist for unreal. Often based around Dear ImGui, and with Epic themselves working on a Slate-based immediate UI plugin (SlateIM).

From experience with different methods, wrapping UMG had the most advantages:

  • We often wanted a hybrid approach between immediate UI and widget blueprints, with parts of the UI written in both.

  • UMG already specifies reflection for all the available properties. Using this reflection means the entirety of UMG is available in EmmsUI, without having to manually re-specify everything that's possible to do in Slate.

  • Sometimes you want to do something advanced that doesn't lend itself well to immediate mode UI. EmmsUI allows retrieving the underlying UMG objects, so it's always possible to do advanced things directly.

Wrapper-Based API Approach

EmmsUI takes a slightly different approach to standard immediate UI:

  • The most common widgets have helper functions, this is your standard mm::Text() or mm::Button()
  • However, individual widgets can still be named to variables to access all their properties

For example, mm::Text() creates a UTextBlock widget, which is wrapped in a mm<UTextBlock> to manage the immediate mode state.

The function mm::Text() has optional parameters for text color and font size and the like, eg:

mm::Text("Large Blue Text", FontSize=20, Color=FLinearColor::Blue);

Only the common properties of UTextBlock are available this way. To add a shadow to the text, for example, you can set this on the wrapped widget afterward:

mm<UTextBlock> LargeText = mm::Text("Large Blue Text", FontSize=20, Color=FLinearColor::Blue);
LargeText.SetShadowColorAndOpacity(FLinearColor::Red);

The mm::Text() helper is itself just a function that calls the generic mm::Widget() call that can be used to wrap any UMG widget type. Without the helper function, the same text widget can be written as:

mm<UTextBlock> LargeText = mm::Widget(UTextBlock);
LargeText.SetText(FText::FromString("Large Blue Text"));
LargeText.SetFontSize(20);
LargeText.SetColorAndOpacity(FLinearColor::Blue);
LargeText.SetShadowColorAndOpacity(FLinearColor::Red);

The mm<> wrapper template manages all of the immediate mode state changes. If you delete one of the property set function calls, it will recognize this and reset the property on the UMG widget back to the default.

Editable State

Helper functions are available for managing editable state, similar to other immediate mode UI libraries.

Some examples:

// Create an editable text box that the user can edit
// UserText will be modified to whatever the user entered
FString UserText;
mm::EditableTextBox(UserText);

// Create a checkbox that the user can click
// bCheckedValue will be modified to whether the user checked the box or not
bool bCheckedValue = false;
mm::CheckBox(bCheckedValue, "Check Box Label");

// Create a spinbox that the user can modify the value of
// NumericVale will be set to whatever the user specified
float NumericValue = 0.0;
mm::SpinBox(NumericValue);

Panel Widgets

Panel widgets are UMG widgets that can contain other widgets. There are two main ways of placing immediate widgets within panels:

Begin()/End() Panel

The function mm::Begin() creates an immediate panel widget, and sets it as active. Subsequent immediate widgets are added into the active panel, until mm::End() is called to deactivate it.

Panel widgets operate as a stack, so you can nest Begin and End calls and it will behave appropriately.

For all C++ panel widgets types, individual helper functions are also bound. For example, mm::BeginHorizontalBox() is equivalent to mm::Begin(UHorizontalBox).

This lets you write code like this:

// A vertical box containing a row with three buttons, and two rows with one button
mm::BeginVerticalBox();
	mm::BeginHorizontalBox(); // First row of the vertical box: a nested horizontal box
		mm::Button("1A"); // Placed within the horizontal box
		mm::Button("1B"); // Placed within the horizontal box
		mm::Button("1C"); // Placed within the horizontal box
	mm::EndHorizontalBox();

	mm::Button("2"); // Second row of the vertical box

	mm::Button("3"); // Third row of the vertical box
mm::EndVerticalBox();

Within() Panel

Many UMG panel widgets only support having one child widget inside them. To make these easier to use, the mm::Within() syntax will create a panel widget which is active for exactly one child. The immediate widget created right after the call to Within will be placed inside the panel, widgets after that will not be.

Just as with mm::Begin(), helper functions are available, so mm::WithinBorder() is equivalent to mm::Within(UBorder).

mm::WithinBorder(FLinearColor::Red); // Create a UBorder with a red background color, and place the next widget inside
mm::WithinSizeBox(Width=200, Height=200); // Create a USizeBox inside the Border, and place the next widget inside
mm::Button("Button Inside Border"); // The button is placed inside the SizeBox

mm::Text("Text Outside Border"); // This text is _not_ placed inside either the border or the sizebox

Slot Properties

Many panel widgets have slots with properties such as padding, alignment, etc To specify slot properties for a widget, call into them right before adding the widget into the panel

For example, to add a button that is centered horizontally within a vertical box, call mm::HAlign_Center() right before adding the button:

mm::BeginVerticalBox();
	mm::HAlign_Center(); // Center the button within the vertical box row
	mm::Button("Centered Button");
mm::EndVerticalBox();

Example: Horizontal Boxes

Some examples of slot properties within a horizontal box:

mm::BeginHorizontalBox();
	mm::Slot_Fill(); // Expand this row to fill as much of the horizontal box as possible
	mm::VAlign_Fill(); // Fill the entire vertical height of the horizontal box
	mm::Padding(10, 2, 2, 2); // Pad by 10px on the left, 2px in the other directions
	mm::Button("BIG BUTTON");
mm::EndHorizontalBox();

Example: Canvas Panels

Some examples of slot properties within a canvas panel:

mm::BeginCanvasPanel();
	mm::Anchors(0.5, 0.5); // Anchor the next widget to the middle of the canvas
	mm::AutoSize(true); // The next widget is auto-sized within the canvas slot
	mm::Offsets(0, 0, 100, 30); // Set the offsets on the canvas panel slot
	mm::Text("Centered Text");
mm::EndCanvasPanel();

Re-entering an Existing Panel

More widgets can be added to an existing panel by storing it in a variable and then calling mm::Begin() on it again later. For example, you can create two vertical boxes next to each other and then add values to them one by one:

mm::BeginHorizontalBox();
	// The created widget is returned from Begin()
	mm<UVerticalBox> LeftBox = mm::BeginVerticalBox();
	mm::EndVerticalBox();

	// Alternatively, you can also explicitly create it without Begin-ing it:
	mm<UVerticalBox> RightBox = mm::Widget(UVerticalBox);
mm::EndHorizontalBox();

// Add the numbers 1 through 10 to both of the columns
for (int i = 1; i <= 10; ++i)
{
	// Add it to the left box
	mm::Begin(LeftBox);
		mm::Text(f"{i}");
	mm::End();

	// Add it to the right box
	mm::Begin(RightBox);
		mm::Text(f"{i}");
	mm::End();
}

Responding to Events

All event handlers on the UMG widgets are automatically exposed through the wrappers. To check whether an event has occurred, call one of the Was...() functions on the mm widget.

For example, to check whether the text in an editable text box has changed, you can call WasTextChanged() on it.

FString CurrentText;
mm<UEditableTextBox> TextInput = mm::EditableTextBox(CurrentText);
if (TextInput.WasTextChanged())
{
	Print(f"The user changed the text to {CurrentText}!");
}

Events with Parameters

Some UMG event handlers will have parameters that you may want to check, beyond just whether the event occurred or not.

For example, the commit handler on the editable text box has the committed text and the commit method. These can be retrieved by passing variables into WasTextCommitted(), which will then be set to the event's parameters:

FText CommittedText;
ETextCommit CommitMethod;
if (TextInput.WasTextCommitted(CommittedText, CommitMethod))
{
	if (CommitMethod == ETextCommit::OnEnter)
	{
		Print(f"User committed the text {CommittedText} by pressing Enter");
	}
}

Binding an Event

The Was...() event functions only check whether the event has happened the previous frame. In cases where you want to detect multiple events, or if you want to respond immediately when it happens, it is possible to bind functions to events as well.

These use the same events but with an On...() prefix, and take an object and a function to bind:

mm<UCheckBox> CheckBox = mm::Widget(UCheckBox);
CheckBox

Related Skills

View on GitHub
GitHub Stars68
CategoryDevelopment
Updated7d ago
Forks19

Languages

C++

Security Score

95/100

Audited on Mar 25, 2026

No findings