Customizing the editor's toolbar buttons menu via custom plugin

Greetings,

I want to do a simple experiment for my learning purposes. I want to extend the editor, by adding a new button in the toolbar menu (next to say QuickSettings), and when clicked it just displays a message box. However, this customization should be done via a plugin. I managed to create a “blank” plugin, and working fine. Now I just want to make it extend the editor a bit, by creating a button and its implementation (displaying a message) residing in my plugin.

I’ve done some research here and wasn’t satisfied. So I’ve done more research, and found something interesting in MultiBox.cpp line 237, which has the ApplyCustomizedBlocks() method. There is this CustomizationData object that probably is what I’m looking for. Yet I’m kind of confused as to how to populate it, and whether or not I’m looking in the right place to begin with.

Could you guys give me any pointers?

1 Like

Hi Seenooh,

So in order to integrate new UI into existing things like the menu bar or the toolbar, you’ll need to take advantage of the extension points system, step one you need to turn on showing the existing extension points in the UI. Then restart the editor.

Alright, now we know the extension names given to the different sections of the toolbar. Now we need to inject our toolbar items where we want them.

This part takes some digging, but the easiest way is to figure out who is creating the toolbar. To do that you should use the Widget Reflector. Window > Developer Tools > Widget Reflector.

Now we know the file and line number that created it, so we we click on the file/line in the Widget Reflector to check it out.

Once in that file, we scroll up a little-ways we see where the toolbar is pulling any extenders from.

FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
	TSharedPtr<FExtender> Extenders = LevelEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders();

Ok, so we now know the LevelEditor Module is the one responsible for creating the toolbar, and also provides the actual accessor for adding extensions to it as well. So in your plugin module when you initialize you’ll add yourself to the list of extenders for the toolbar.

TSharedPtr<FExtender> MyExtender = MakeShareable(new FExtender);
MyExtender->AddToolBarExtension("Settings", EExtensionHook::After, NULL, FToolBarExtensionDelegate::CreateRaw(this, &MyPlugin::AddToolbarExtension));

FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(MyExtender);

It would look something like that, your extender’s delegate will be called when the toolbar is being created so that you can create whatever widgets you need to appear in the toolbar. Note the importance of the “Settings” string, that’s the extension hook we found after turning on show extension points. If there is no extension area with that name your extension will never be called.

Also, make sure you save the reference to the MyExtender, you’ll need to remember to unregister it from the toolbar extender manager for the LevelEditor module when your module is unloaded.

So that’s the crash course in extending the existing editor UI from a plugin. Can’t wait to see what you make!

Cheers,
(Epic Games)

1 Like

Wow, ! It’s really very straightforward!

I just need a little help with the declaration of AddToolbarExtension() method that I should define in my plugin. Excuse my noobish question as I’m a bit rusty with C++. I merely wanted a starting point (like what you provided) and from there I will pick it up.

I went to the definition of CreateRaw() and it confused tbh. I’d appreciate simply telling me how to declare AddToolbarExtension() and that’ll be it!

Thanks alot for taking the time to answer me with great detail.

Edit:

For anyone reading this, you should declare AddToolBarExtension() as follows:

void AddToolbarExtension(FToolBarBuilder& Builder);

Implement that, put a UE_LOG() and you should see your log in the output window.

Thanks a lot .

Glad to be of assistance :slight_smile:

Hello again ,

I’m trying to wrap up my experiment, by adding a new button and having it display a log message.

Here’s what I did so far:

I defined a new commad in my plugin’s header:

TSharedPtr< FUICommandInfo > MyButton;

In the StartupModule(), and after adding my new extender, I put the following:

TSharedPtr<FUICommandList> LevelEditorCommands = MakeShareable(new FUICommandList);

LevelEditorCommands->MapAction(
		MyButton,
		FExecuteAction::CreateStatic(&MyButton_Clicked, EToolkitMode::Standalone, LevelEditorModule.GetLevelEditorInstance(), false)); 

I’m trying to map the MyButton command to a callback function I defined in my plugin header like this:

static void MyButton_Clicked(const EToolkitMode::Type ToolkitMode, TWeakPtr< SLevelEditor > LevelEditor, bool bConfirmMultiple);

I implemented it to simply print a log message.

Now the problem is, and according to my research, that there is one missing step, which is registering the command. I need to have a RegisterCommands() function, where I should define in it something like this:

UI_COMMAND(MyButton, "My Button", "Displays a log message", EUserInterfaceActionType::Button, FInputGesture());

My question is how do I get RegisterCommands() defined, and is there any more things I’m missing?

Thank you for the help!

Note: I implemented AddToolbarExtension() like this.

void HamadsPluginModule::AddToolbarExtension(FToolBarBuilder &builder)
{
#define LOCTEXT_NAMESPACE "LevelEditorToolBar"

	UE_LOG(MyPlugin, Log, TEXT("Starting Extension logic"));

	
	builder.AddToolBarButton(MyButton, NAME_None, LOCTEXT("WorldProperties_Override", "My Button"), LOCTEXT("WorldProperties_ToolTipOverride", "Click me to display a message"), TAttribute<FSlateIcon>(), "LevelToolbarWorldSettings");

#undef LOCTEXT_NAMESPACE
}

Hello again,

I’m sorry but the following line is absolutely wrong:

TSharedPtr<FUICommandList> LevelEditorCommands = MakeShareable(new FUICommandList);

I think what I actually need to is to pull the current level editor commands.

FLevelEditorCommands LevelEditorCommands = LevelEditorModule.GetLevelEditorCommands();

FUICommandInfoDecl cmdInfo = LevelEditorCommands.NewCommand("MyButton", LOCTEXT("WorldProperties_Override", "My Button"), LOCTEXT("WorldProperties_ToolTipOverride", "Displays log mesg"));

If I’m approaching this right this time, I have a problem and a question:

GetLevelEditorCommands() only return a const reference, and I cannot really do anything. Is there another way of pulling the editor’s commands, while being editable/append-able?

The question is, what do I do next with cmdInfo?

Hi Seenooh, you were on the right track, but you don’t add to another modules commands, you need to derive from TCommands, take a look at an existing one like FBlueprintEditorCommands. After you’ve done that, you’ll register them by calling FYourPluginCommands::Register(); That will call RegisterCommands.

Hi !

So now I followed your instructions, and I created a seperate class for my plugin commands.

The header:

#pragma once
#include "LevelEditor.h"

DECLARE_LOG_CATEGORY_EXTERN(MyPlugin, Log, All);
DEFINE_LOG_CATEGORY(MyPlugin);

class FHamadsPluginCommands : public TCommands<FHamadsPluginCommands>
{
public:

	FHamadsPluginCommands()
		: TCommands<FHamadsPluginCommands>(TEXT("HamadsPlugin"), NSLOCTEXT("Contexts", "HamadsPlugin", " Plugin"), NAME_None, FEditorStyle::GetStyleSetName())
	{
		}

	virtual void RegisterCommands() OVERRIDE;

	static void BindCommands();

	TSharedPtr< FUICommandInfo > MyButton;

	static void MyButton_Clicked();

	TSharedPtr<FUICommandList> MyPluginCommands;
};

The Source:

#include "HamadsPluginPrivatePCH.h"

#include "FHamadsPluginCommands.h"
 
PRAGMA_DISABLE_OPTIMIZATION
void FHamadsPluginCommands::RegisterCommands()
{
	UI_COMMAND(MyButton, "My Button", "Displays a message in output log", EUserInterfaceActionType::Button, FInputGesture());
}
PRAGMA_ENABLE_OPTIMIZATION

void FHamadsPluginCommands::BindCommands()
{
	MyPluginCommands = MakeShareable(new FUICommandList);

	MyPluginCommands->MapAction(
		MyButton,
		FExecuteAction::CreateSP(this, &FHamadsPluginCommands::MyButton_Clicked),
		FCanExecuteAction());
}

void FHamadsPluginCommands::MyButton_Clicked() 
{
	UE_LOG(MyPlugin, Log, TEXT("Button is clicked!"));
}

The Problem

I can’t compile. There is a problem with CreateSP(). I ran out of ideas to solve this.

The line:

MyPluginCommands->MapAction(
	MyButton,
	FExecuteAction::CreateSP(this, &FHamadsPluginCommands::MyButton_Clicked),
	FCanExecuteAction());**

The error:

Error 1 error C2665: ‘TBaseDelegate_NoParams::CreateSP’ : none of the 2 overloads could convert all the argument types D:\000-MyProjects\UE4\UnrealEngine\Engine\Plugins\HamadsPlugin\Source\HamadsPlugin\Private\FHamadsPluginCommands.cpp 18 1 UE4

Thank you for your help so far!

Edit: What should I do to “MyPluginCommands” after I map the action? Is there something I need to do for it or will that just be enough?

Ok, I moved some stuff over to my module. The relevant snippet from my module looks like this now:

void HamadsPluginModule::StartupModule()
{
#define LOCTEXT_NAMESPACE “LevelEditorToolBar”

UE_LOG(MyPlugin, Log, TEXT(" plugin started!"));

TSharedPtr<FExtender> MyExtender = MakeShareable(new FExtender);
MyExtender->AddToolBarExtension("Settings", EExtensionHook::After, NULL, FToolBarExtensionDelegate::CreateRaw(this, &HamadsPluginModule::AddToolbarExtension));

FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(MyExtender);


MyPluginCommands = MakeShareable(new FUICommandList);

MyPluginCommands->MapAction(
	FHamadsPluginCommands::Get().MyButton,
	FExecuteAction::CreateSP(this, &HamadsPluginModule::MyButton_Clicked),
	FCanExecuteAction());

#undef LOCTEXT_NAMESPACE
}

void HamadsPluginModule::MyButton_Clicked() ToolkitMode
{
UE_LOG(MyPlugin, Log, TEXT(“Button is clicked!”));
}

Now, I’m getting a new error:

Error 1 error C2039: ‘AsShared’ : is not a member of ‘HamadsPluginModule’ d:\000-myprojects\ue4\unrealengine\engine\source\runtime\core\public\templates\DelegateSignatureImpl.inl 306 1 UE4

I’m pulling my hair right now. :stuck_out_tongue:

The MapAction should be done in a module or somewhere else, not in the TCommands class :smiley:

TCommands is just the definition of the commands. The system expects UIs to be created, and when those specific instances are created, they would perform their own unique versions of MapAction calls, because each piece of UI needs a different callback to the same commands, but depending on who has focus controls which CommandList gets called.

CreateSP means CreateSmartPointer delegate, in order to do that from a raw pointer it needs that pointer to derive from TSharedFromThis. Just use CreateRaw or CreateStatic instead.

Fixed, thanks!

After going through another series of nightmares, I finally managed to wrap up my experiment successfully. I added a button, and when clicked displays a log message. All handled within the plugin!

Thank you once again for your patience with me. It was a great ride getting to this point.

Hello!

I just want to share a tutorial I put together after finishing this experiment:

Hope you find that useful. :slight_smile:

Another example and tutorial here…

CreateSHAREDPointer…