x

Search in
Sort by:

Question Status:

Search help

  • Simple searches use one or more words. Separate the words with spaces (cat dog) to search cat,dog or both. Separate the words with plus signs (cat +dog) to search for items that may contain cat but must contain dog.
  • You can further refine your search on the search results page, where you can search by keywords, author, topic. These can be combined with each other. Examples
    • cat dog --matches anything with cat,dog or both
    • cat +dog --searches for cat +dog where dog is a mandatory term
    • cat -dog -- searches for cat excluding any result containing dog
    • [cats] —will restrict your search to results with topic named "cats"
    • [cats] [dogs] —will restrict your search to results with both topics, "cats", and "dogs"

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?

Product Version: Not Selected
Tags:
more ▼

asked Apr 06 '14 at 09:23 PM in C++ Programming

avatar image

LordNyte
103 10 12 16

(comments are locked)
10|2000 characters needed characters left

4 answers: sort voted first

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.

alt text

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.

alt text

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, Nick (Epic Games)

more ▼

answered Apr 06 '14 at 10:44 PM

avatar image

Nick Darnell ♦♦ STAFF
6k 77 36 150

avatar image LordNyte Apr 06 '14 at 11:42 PM

Wow, Nick! 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 Nick.

avatar image Nick Darnell ♦♦ STAFF Apr 07 '14 at 03:34 AM

Glad to be of assistance :)

(comments are locked)
10|2000 characters needed characters left

Hello!

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

https://forums.unrealengine.com/showthread.php?2916-Tutorial-Intro-to-Editor-Customization-with-Plugins

Hope you find that useful. :)

more ▼

answered Apr 17 '14 at 11:00 AM

avatar image

LordNyte
103 10 12 16

(comments are locked)
10|2000 characters needed characters left

Hello again Nick,

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
 }
more ▼

answered Apr 07 '14 at 09:35 PM

avatar image

LordNyte
103 10 12 16

avatar image LordNyte Apr 08 '14 at 10:58 AM

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?

avatar image Nick Darnell ♦♦ STAFF Apr 08 '14 at 02:02 PM

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.

avatar image LordNyte Apr 08 '14 at 11:01 PM

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", "Hamads 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!"));
 }

avatar image LordNyte Apr 08 '14 at 11:01 PM

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?

avatar image Nick Darnell ♦♦ STAFF Apr 08 '14 at 11:05 PM

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

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.

avatar image LordNyte Apr 08 '14 at 11:15 PM

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("Hamads 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. :P

avatar image Nick Darnell ♦♦ STAFF Apr 09 '14 at 01:20 AM

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.

avatar image LordNyte Apr 09 '14 at 02:46 AM

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.

avatar image DanHollingsworth Mar 13 '18 at 07:47 PM

CreateSHAREDPointer..

(comments are locked)
10|2000 characters needed characters left
more ▼

answered Feb 09 '15 at 01:28 AM

avatar image

SND R Keene
923 29 117 111

(comments are locked)
10|2000 characters needed characters left
Your answer
toggle preview:

Up to 5 attachments (including images) can be used with a maximum of 5.2 MB each and 5.2 MB total.

Follow this question

Once you sign in you will be able to subscribe for any updates here

Answers to this question