Skip to content

UE uses path-based expanding menu.

Record how to implement path-based extension menus in UE

If you are not familiar with the UE extension menu, it is recommended to take a quick look at: UE Extension Editor Menu

This text is based on the plugin: UE.EditorPlus

Node Management

Manage the menu according to the structure of a tree, where parent nodes can include children:

class EDITORPLUS_API FEditorPlusMenuBase: public TSharedFromThis<FEditorPlusMenuBase>
{
protected:
    // sub menus
    TArray<TSharedRef<FEditorPlusMenuBase>> Children;
}

Create child nodes while creating the parent node:

void FEditorPlusMenuBase::Register(FMenuBuilder& MenuBuilder)
{
    for (const auto Menu: Children)
    {
        Menu->Register(MenuBuilder);
    }
}

Of course, the specific creation behavior of each node may be a little different, override the virtual function to achieve it:

// Menubar
void FEditorPlusMenuBar::Register(FMenuBarBuilder& MenuBarBuilder)
{
    MenuBarBuilder.AddPullDownMenu(
        GetFriendlyName(),
        GetFriendlyTips(),
        // Delegate to call Register
        FEditorPlusMenuManager::GetDelegate<FNewMenuDelegate>(GetUniqueId()),
        Hook);
}

// Section
void FEditorPlusSection::Register(FMenuBuilder& MenuBuilder)
{
    MenuBuilder.BeginSection(Hook, GetFriendlyName());
    FEditorPlusMenuBase::Register(MenuBuilder);
    MenuBuilder.EndSection();
}

// Separator
void FEditorPlusSeparator::Register(FMenuBuilder& MenuBuilder)
{
    MenuBuilder.AddMenuSeparator(Hook);
    FEditorPlusMenuBase::Register(MenuBuilder);
}

// SubMenu
void FEditorPlusSubMenu::Register(FMenuBuilder& MenuBuilder)
{
    MenuBuilder.AddSubMenu(
        GetFriendlyName(),
        GetFriendlyTips(),
        FNewMenuDelegate::CreateSP(this, &FEditorPlusSubMenu::MakeSubMenu),
        false,
        FSlateIcon(),
        true,
        Hook
    );
}

// Command
void FEditorPlusCommand::Register(FMenuBuilder& MenuBuilder)
{
    MenuBuilder.AddMenuEntry(
        CommandInfo->Label, CommandInfo->Tips, CommandInfo->Icon,
        CommandInfo->ExecuteAction, CommandInfo->Hook, CommandInfo->Type);
}

// ......

Generate nodes through the path

Organize the menu in a tree structure, and the path format can define the tree structure of a menu:

"/<Hook>Help/<MenuBar>BarTest/<SubMenu>SubTest/<Command>Action"

The above path can be used to define a series of menu creations:

  • <Hook>Help: Located after the menu named Help in the Hook.
  • <MenuBar>BarTest: Create a menu of type MenuBar with the name BarTest
  • <SubMenu>SubTest: create a sub-item of type SubMenu, named SubTest
  • <Command>Action: Finally, create a command

The interface call format can be very concise:

const FString Path = "/<Hook>Help/<MenuBar>BarTest/<SubMenu>SubTest/<Command>Action";
FEditorPlusPath::RegisterPathAction(
    Path,
    FExecuteAction::CreateLambda([]
    {
        // do action
    })
);

Generate nodes from custom forms

We still retain the cumbersome way of creating menus, which allows for more detailed settings. The organization of the code is somewhat similar to the UE SlateUI style.

EP_NEW_MENU(FEditorPlusMenuBar)("BarTest")
->RegisterPath()
->Content({
    EP_NEW_MENU(FEditorPlusSubMenu)("SubTest")
    ->Content({
        EP_NEW_MENU(FEditorPlusCommand)("Action")
        ->BindAction(FExecuteAction::CreateLambda([]
            {
                // do action
            })),
    })
});

Mixed Form

Of course, the path format itself and the custom-generated menu are the same, and they can be used interchangeably, providing great flexibility.

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

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

Multiple menus defined in different places will be merged into the same tree structure, and nodes with the same name will be considered the same node. In other words, the path is unique, and the same path can uniquely determine a menu node. So we can also identify the nodes and make some adjustments and modifications.

// set Name and Tips
FEditorPlusPath::GetNodeByPath("/<MenuBar>BarTest")->SetFriendlyName(LOCTEXT("MenuTest", "MenuTest"))->SetFriendlyTips(LOCTEXT("MenuTestTips", "MenuTestTips"));

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 is translated using ChatGPT, please feedback if any omissions.