Skip to content

UE Editor Plugin UE.EditorPlus Documentation

Introduction video

Plugin source code

UE.EditorPlus

Mall download

EditorPlus

Project Add Source Code Plugin EU.EditorPlus

Reference document:

Chinese: UE adds plugins through plugin source code - English: UE adds plugins through the plugin source code

Plugin Description

UE.EditorPlus is a UE editor plugin that provides a convenient way to expand editor menus and supports advanced ways to extend them, while also including some practical editor tools. This plugin is compatible with UE5.3+.

Extend editor menu

Explanation

Support multiple ways to expand the editor menu:

Path method: RegisterPathAction("/<MenuBar>Bar/<SubMenu>SubMenu/<Command>Action") - Instantiation method: EP_NEW_MENU(FEditorPlusMenuBar)("Bar") Mixed method: RegisterPath("/<MenuBar>Bar/<SubMenu>SubMenu/<Command>Action",EP_NEW_MENU(FEditorPlusCommand)("Action")

Pathway

You can register an editor menu command in this way:

FEditorPlusPath::RegisterPathAction(
    "/<MenuBar>Bar/<SubMenu>SubMenu/<Command>Action",
    FExecuteAction::CreateLambda([]
        {
            // do action
        })
);

In this way, you can add a menu called Bar behind the Help menu in the editor menu bar, with a submenu named SubMenu in Bar, and then add a command called Action in SubMenu.

The complete path format would be: /<Hook>HookName/<Type1>Name1/<Type2>Name2, where the first path must be <Hook>. The currently supported types and limitations are:

  • <Hook>: Indicates where the menu should be generated in the specified Hook, and subsequent paths cannot include <Hook>.
  • <MenuBar>: Menu bar, the following path cannot have <Hook>, <MenuBar>, <ToolBar>
  • <ToolBar>: Toolbar, the path following it cannot contain <Hook>, <MenuBar>, <ToolBar>
  • <Section>: Menu section, the following path cannot contain <Hook>, <MenuBar>, <Section>
  • <Separator>: Menu separator, the subsequent path should not contain <Hook>, <MenuBar>
  • <SubMenu>: Submenu, the following path cannot contain <Hook>, <MenuBar>
  • <Command>: Menu command, no paths are allowed after it.
  • <Widget>: More customizable Slate UI components that can be extended, with no paths allowed afterwards.

A simpler path format: /BarName/SubMenuName1/SubMenuName2/CommandName, if no type is specified, the first in the path is <MenuBar>, the middle one is <SubMenu>, and the last one is <Command>.

If no <Hook> is specified, <Hook>Help will be automatically added at the very front, indicating that a menu bar will be added after the Help menu.

Instantiation Method

The path method automatically instantiates all nodes based on type and default parameters. We can also control the instantiation ourselves, allowing for more detailed control over the content of the extensions.

EP_NEW_MENU(FEditorPlusMenuBar)("MyBar", "MyBar", LOCTEXT("MyBar", "MyBar"), LOCTEXT("MyBarTips", "MyBarTips"))
->RegisterPath()
->Content({
    EP_NEW_MENU(FEditorPlusSubMenu)("MySubMenu")
    ->Content({
        EP_NEW_MENU(FEditorPlusCommand)("MyAction")
        ->BindAction(FExecuteAction::CreateLambda([]
            {
                // do action
            })),
    })
});

When instantiating MyBar, you can pass in the hook name, localized name, and localized tooltip parameters ("MyBar", LOCTEXT("MyBar", "MyBar"), LOCTEXT("MyBarTips", "MyBarTips")). The above code is equivalent to the path format /<Hook>Help/<MenuBar>MyBar/<SubMenu>MySubMenu/<Command>MyAction.

Mixed mode

Of course, you can also mix and match the two methods:

FEditorPlusPath::RegisterPath(
    "/<MenuBar>Bar/<SubMenu>SubMenu/<Command>Action",
    EP_NEW_MENU(FEditorPlusCommand)("Action")
    ->BindAction(FExecuteAction::CreateLambda([]
        {
            // do action
        })),
);

In this situation, the plugin will automatically instantiate the nodes along the intermediate path, while the user's own instantiated nodes will be used for the final path.

More use cases

Header file:

#include <EditorPlusPath.h>

Specify the localization language through the path method. EP_FNAME_HOOK_AUTO automatically uses the path name as the Hook name:

FEditorPlusPath::RegisterPathAction(
        "/Bar/Action",
        FExecuteAction::CreateLambda([]
        {
            // do action
        }),
        EP_FNAME_HOOK_AUTO,
        LOCTEXT("Action", "Action"),
        LOCTEXT("ActionTips", "ActionTips"));

Obtain nodes through the path and set localized text:

FEditorPlusPath::GetNodeByPath("/MenuTest")
    ->SetFriendlyName(LOCTEXT("MenuTest", "MenuTest"))
    ->SetFriendlyTips(LOCTEXT("MenuTestTips", "MenuTestTips"));

Add a Slate UI control at the end of the path.

FEditorPlusPath::RegisterPath(
    "/<MenuBar>Bar/<SubMenu>SubMenu/<Widget>Widget",
    EP_NEW_MENU(FEditorPlusWidget)("Widget", LOCTEXT("Widget", "Widget"))
        ->BindWidget(SNew(SHorizontalBox)));
);

Add new nodes in the built-in hooks of Unreal Engine.

FEditorPlusPath::RegisterPath("<Hook>EpicGamesHelp/<Separator>ExtendSeparator")

Declaring the same path multiple times is recognized as the same path, thus allowing for the continuous expansion of that path.

FEditorPlusPath::RegisterPathAction("/MenuTest/SubMenu1/SubMenu1/Path1", Action, EP_FNAME_HOOK_AUTO, LOCTEXT("Path1", "Path1"), LOCTEXT("Path1Tips", "Path1Tips"));
FEditorPlusPath::RegisterPathAction("/MenuTest/SubMenu1/SubMenu1/Path2", Action, EP_FNAME_HOOK_AUTO, LOCTEXT("Path2", "Path2"), LOCTEXT("Path2Tips", "Path2Tips"));

Continue to expand the path for a node.

auto node = FEditorPlusPath::GetNodeByPath("/MenuTest");
FEditorPlusPath::RegisterChildPath(node, "<SubMenu>Sub/<Separator>Sep");

Delete a path

FEditorPlusPath::UnregisterPath("/MenuTest/SubMenu1/SubMenu1/Path1");

Expand Toolbar

FEditorPlusPath::RegisterPath("/<Hook>ProjectSettings/<ToolBar>MenuTestToolBar")
->Content({
    EP_NEW_MENU(FEditorPlusCommand)("ToolBarCommand1")
    ->BindAction(...)
});

Interface Description

class EDITORPLUS_API FEditorPlusPath
{
public:

    static TSharedPtr<FEditorPlusMenuBase> RegisterPath(const FString& Path, const TSharedPtr<FEditorPlusMenuBase>& Menu=nullptr);
    static TSharedPtr<FEditorPlusMenuBase> RegisterPath(const FString& Path, const FText& FriendlyName, const FText& FriendlyTips);
    static TSharedPtr<FEditorPlusMenuBase> RegisterPathAction(
        const FString& Path, const FExecuteAction& ExecuteAction, const FName& Hook=EP_FNAME_HOOK_AUTO,
        const FText& FriendlyName=FText::GetEmpty(), const FText& FriendlyTips=FText::GetEmpty());

    static TSharedPtr<FEditorPlusMenuBase> RegisterChildPath(
        const TSharedRef<FEditorPlusMenuBase>& InParent, const FString& Path, const TSharedPtr<FEditorPlusMenuBase>& Menu=nullptr);
    static TSharedPtr<FEditorPlusMenuBase> RegisterChildPath(
        const TSharedRef<FEditorPlusMenuBase>& InParent, const FString& Path, const FText& FriendlyName, const FText& FriendlyTips);
    static TSharedPtr<FEditorPlusMenuBase> RegisterChildPathAction(
        const TSharedRef<FEditorPlusMenuBase>& InParent, const FString& Path, const FExecuteAction& ExecuteAction,
        const FName& Hook=EP_FNAME_HOOK_AUTO, const FText& FriendlyName=FText::GetEmpty(), const FText& FriendlyTips=FText::GetEmpty());

    static bool UnregisterPath(
        const FString& Path, const TSharedPtr<FEditorPlusMenuBase>& Leaf=nullptr);

    static TSharedPtr<FEditorPlusMenuBase> GetNodeByPath(const FString& Path);
};
  • RegisterPath: Generate Path Menu RegisterPathAction: Generate a path menu and automatically bind an action to the end <Command> node. RegisterChildPath: Generate child paths for the specified node.
  • RegisterChildPathAction: Continues to generate child paths for the specified node and automatically binds operations.
  • UnregisterPath: Deletes the path. The Leaf can specify strict matching when there are multiple end nodes with the same name. During the deletion process, it will backtrack to intermediate nodes, and any intermediate node without any child nodes will also be deleted.
  • GetNodeByPath: Get node by path

Node type

// base class of all node
class EDITORPLUS_API FEditorPlusMenuBase: public TSharedFromThis<FEditorPlusMenuBase> {}

class EDITORPLUS_API FEditorPlusHook: public TEditorPlusMenuBaseRoot {}

class EDITORPLUS_API FEditorPlusMenuBar: public TEditorPlusMenuBaseNode {}

class EDITORPLUS_API FEditorPlusToolBar: public TEditorPlusMenuBaseNode {}

class EDITORPLUS_API FEditorPlusSection: public TEditorPlusMenuBaseNode {}

class EDITORPLUS_API FEditorPlusSeparator: public TEditorPlusMenuBaseNode{}

class EDITORPLUS_API FEditorPlusSubMenu: public TEditorPlusMenuBaseNode {}

class EDITORPLUS_API FEditorPlusCommand: public TEditorPlusMenuBaseLeaf {}

class EDITORPLUS_API FEditorPlusWidget: public TEditorPlusMenuBaseLeaf {}

For more examples and interface descriptions, please refer to the source code UE.EditorPlusTest case MenuTest.cpp

Modular Management

UE.EditorPlus also provides a modular framework for managing extension menus, supporting the automatic loading and unloading of extension menus when plugins are loaded and unloaded.

Allow the menu class to inherit from IEditorPlusToolInterface and override the OnStartup and OnShutdown functions. OnStartup is responsible for creating the menu, while OnShutdown is responsible for calling the node's Destroy function to clean up the menu. When the reference count of a single node reaches 0, automatic cleanup will be executed.

class FMenuTest: public IEditorPlusToolInterface
{
public:
    virtual void OnStartup() override;
    virtual void OnShutdown() override;
}

void FMenuTest::OnStartup()
{
    BuildPathMenu();
    BuildCustomMenu();
    BuildMixMenu();
    BuildExtendMenu();
}

void FMenuTest::OnShutdown()
{
    for(auto Menu: Menus)
    {
        if(Menu.IsValid()) Menu->Destroy();
    }
    Menus.Empty();
}

The menu management class inherits from IEditorPlusToolManagerInterface and overrides the AddTools function, where it adds the menu class.

class FEditorPlusToolsImpl: public IEditorPlusToolManagerInterface
{
public:
    virtual void AddTools() override;
}

void FEditorPlusToolsImpl::AddTools()
{
    if (!Tools.Num())
    {
        Tools.Emplace(MakeShared<FMenuTest>());
    }

}

The StartupTools and ShutdownTools functions of the management class are called when loading and unloading plugins, respectively.

void FEditorPlusToolsModule::StartupModule()
{
    Impl = FEditorPlusToolsImpl::Get();
    Impl->StartupTools();

}
void FEditorPlusToolsModule::ShutdownModule()
{
    Impl->ShutdownTools();
}

Once the above adaptation is completed, the extended menu can be automatically loaded and unloaded when loading and unloading the plugins.

Editor tool

UE.EditorPlus also provides some practical editor tools.

Create editor window

With EditorPlus, you can easily create a new editor window.

// register spawn tab
Tab = MakeShared<FEditorPlusTab>(LOCTEXT("ClassBrowser", "ClassBrowser"), LOCTEXT("ClassBrowserTip", "Open the ClassBrowser"));
Tab->Register<SClassBrowserTab>();

// register menu action to spawn tab
FEditorPlusPath::RegisterPathAction(
    "/EditorPlusTools/ClassBrowser",
    FExecuteAction::CreateSP(Tab.ToSharedRef(), &FEditorPlusTab::TryInvokeTab),
);

SClassBrowserTab is a custom UI control.

class SClassBrowserTab final : public SCompoundWidget
{
    SLATE_BEGIN_ARGS(SClassBrowserTab)
    {}
    SLATE_END_ARGS()
    // ...
}

ClassBrowser

ClassBrowser is a UE Class viewer, which can be opened through the menu EditorPlusTools -> ClassBrowser.

Based on UE reflection, it is convenient to view various types of UE member information, explanatory prompts, etc. It supports fuzzy search and can jump to open parent class information.

MenuCollections is a tool for quickly searching and saving menu commands, helping you find the commands you need to execute quickly and allowing you to save frequently used commands to enhance efficiency.

SlateResourceBrowser

SlateResourceBrowser is a tool that allows you to quickly view Slate UI resources, helping you browse and find the editor resources you need, making it convenient to expand the editor.

Original: https://wiki.disenone.site/en

This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.

This post was translated using ChatGPT, please provide feedbackPlease point out any omissions.