Activatable Widgets

[ ue4  widget  ]
Written on December 9, 2022

The CommonUI plugin for Unreal Engine has some really useful features. One of them is the notion of activatable widgets, and the activatable widget stack.

The Lyra starter project uses CommonUI and is in some ways a good reference for making the best use of it. You could copy the CommonGame plugin from it to use in your projects, but I feel it is doing too much, and pulls in more dependencies than I like. Also, I couldn’t find any documentation on their UI setup, even though many other things are described here.

So, I’ve started a simple Unreal project to help me better understand CommonUI and allow me to experiment with it, rather than doing this in the game I’m working on. Hopefully others will benefit from this as well - it is available on GitHub: https://github.com/snorristurluson/CommonUIPlayground

Screenshot

Window Manager

I use a subsystem as a convenient way of accessing the widget stack from anywhere in code or blueprints. Calling it a window manager may be a stretch at this point, but I have plans for developing this further in the future.

class COMMONUIPLAYGROUND_API UWindowManager : public UGameInstanceSubsystem
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable)
	void SetBaseLayer(UCommonActivatableWidgetContainerBase* InBaseLayer);

	UFUNCTION(BlueprintCallable)
	UCommonActivatableWidget* PushModal(TSubclassOf<UCommonActivatableWidget> ActivatableWidgetClass);

	UFUNCTION(BlueprintCallable)
	void PopModal();

protected:
	UCommonActivatableWidgetContainerBase* BaseLayer = nullptr;
};

The PushModal and PopModal methods are simple wrappers around the AddWidget and RemoveWidget methods the UCommonActivatableWidgetContainerBase class.

UCommonActivatableWidget* UWindowManager::PushModal(TSubclassOf<UCommonActivatableWidget> ActivatableWidgetClass)
{
	return BaseLayer->AddWidget(ActivatableWidgetClass);
}

void UWindowManager::PopModal()
{
	UCommonActivatableWidget* TopWidget = BaseLayer->GetActiveWidget();
	if (TopWidget)
	{
		BaseLayer->RemoveWidget(*TopWidget);
	}
}

Handling actions

I have a main screen that responds to the Escape key by popping up a menu.

UCLASS()
class COMMONUIPLAYGROUND_API UUIMainScreen : public UMyActivatableWidget
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintNativeEvent)
	void HandleEscapeAction();

protected:
	virtual void NativeOnInitialized() override;
};

I set up an action tag in CPP code to handle the Escape button. In NativeOnInitialized an action binding is registered.

static const FUIActionTag EscapeActionTag = FUIActionTag::AddNativeTag(TEXT("Escape"));

void UUIMainScreen::NativeOnInitialized()
{
	Super::NativeOnInitialized();
	RegisterUIActionBinding(FBindUIActionArgs(EscapeActionTag, false, FSimpleDelegate::CreateUObject(this, &ThisClass::HandleEscapeAction)));
}

Binding the Escape UI action to the Esc key happens in the Project Settings|Plugins|Common UI Input Settings.

EscapeActionSetup EscapeActionSetup

I set up a blueprint class that inherits from the UUIMainScreen class, and implement the HandleEscapeAction method there:

HandleEscapeAction HandleEscapeAction

The blueprint also sets up the container to use for any modal popups:

OnInitialized OnInitialized

Freeing the cursor

I have the project set up so that the mouse is capture and the cursor is not shown, and the camera is controlled by the mouse. When I hit escape, I want to show the mouse cursor and detach the camera from it, so that I can interact with the UI.

This can be accomplished by overriding the GetDesiredInputConfig method from UCommonActivatableWidget. To make this easier to do from blueprints, I’ve used a similar approach to what is done in Lyra.

UCLASS()
class COMMONUIPLAYGROUND_API UMyActivatableWidget : public UCommonActivatableWidget
{
	GENERATED_BODY()

public:
	virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	EMyInputMode InputMode = EMyInputMode::All;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	EMouseCaptureMode CaptureMode = EMouseCaptureMode::NoCapture;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	bool HideCursor = false;
};
TOptional<FUIInputConfig> UMyActivatableWidget::GetDesiredInputConfig() const
{
	return FUIInputConfig(ECommonInputMode(InputMode), CaptureMode, HideCursor);
}

If I base my widgets off of this one, I can now control the mouse behavior whenever that widget is active by setting the input mode, capture mode and the hide cursor flag.

Note that I had to provide my own input mode enum - if I tried to use ECommonInputMode directly I got a linker error. Maybe I’m doing something wrong, but I gave up and added my own by copying theirs, and just casting it.