目标
在UE4中,一个界面命令对应一个FUICommandInfo
,他可以由多种方式触发,比如快捷键或者菜单按钮。本篇的目标是观察它扩展编辑器界面的流程,即:如何创建一个界面命令?如何与一个具体的代码操作对应起来?如何确实地添加到编辑器的某一处上?随后实践。
参考的目标主要是插件模板:编辑器工具栏按钮,以及AssetManagerEditor这个内建的插件(它实现了显示Asset的引用关系的功能)
基本流程概括
概括讲:
一个FUICommandInfo
将被放到一个TCommands
(继承自FBindingContext
)里并在其中被创建,随后FUICommandList
将一个FUICommandInfo
与一个FUIAction
(命令具体实施的操作)对应起来。最后,编辑器某处界面将添加一个“扩展”,在这个“扩展”中指定了FUICommandList
是哪个,以及将为哪几个FUICommandInfo
添加界面元素。
可表示为下图:
出现的包含Command字样的类比较多,下面分别观察下他们的角色:
FUICommandInfo
一个FUICommandInfo
实际对应一种命令,它有如下成员变量:
/** Input commands that executes this action */
TArray<TSharedRef<FInputChord>> ActiveChords;
/** Default display name of the command */
FText Label;
/** Localized help text for this command */
FText Description;
/** The default input chords for this command (can be invalid) */
TArray<FInputChord> DefaultChords;
/** Brush name for icon to use in tool bars and menu items to represent this command */
FSlateIcon Icon;
/** Brush name for icon to use in tool bars and menu items to represent this command in its toggled on (checked) state*/
FName UIStyle;
/** Name of the command */
FName CommandName;
/** The context in which this command is active */
FName BindingContext;
/** The type of user interface to associated with this action */
EUserInterfaceActionType UserInterfaceType;
TCommands
/** A base class for a set of commands. Inherit from it to make a set of commands. See MainFrameActions for an example. */
template<typename CommandContextType>
class TCommands : public FBindingContext
TCommands
是一个基类,代表了一组命令。你需要继承它来实现一组新的命令。(可以看\Engine\Source\Editor\MainFrame\Private\Frame\MainFrameActions.h
中的示例)。
它的基类FBindingContext
:
/**
* Represents a context in which input bindings are valid
*/
class SLATE_API FBindingContext
: public TSharedFromThis<FBindingContext>
代表了一个输入绑定有效的环境。
(关于这个类的意义还有待更深入研究)
FUICommandList
FUICommandList
中有一个成员,将存储命令所对应的操作。
typedef TMap< const TSharedPtr< const FUICommandInfo >, FUIAction > FUIBindingMap;
/** Mapping of command to action */
FUIBindingMap UICommandBindingMap;
它拥有多个不同版本的MapAction
用来将一个命令与操作对应,其中一个版本如下:
/**
* Maps a command info to a series of delegates that are executed by a multibox or mouse/keyboard input
*
* @param InUICommandInfo The command info to map
* @param ExecuteAction The delegate to call when the command should be executed
* @param RepeatMode Can this action can be repeated if the chord used to call it is held down?
*/
void MapAction( const TSharedPtr< const FUICommandInfo > InUICommandInfo, FExecuteAction ExecuteAction, EUIActionRepeatMode RepeatMode = EUIActionRepeatMode::RepeatDisabled );
FUIAction
FUIAction
对应了一个实际的操作
/**
* Implements an UI action.
*/
struct SLATE_API FUIAction
它的成员变量包含各种 delegate,指出这个操作将执行什么函数、能否执行等。
/** Holds a delegate that is executed when this action is activated. */
FExecuteAction ExecuteAction;
/** Holds a delegate that is executed when determining whether this action can execute. */
FCanExecuteAction CanExecuteAction;
/** Holds a delegate that is executed when determining the check state of this action. */
FGetActionCheckState GetActionCheckState;
/** Holds a delegate that is executed when determining whether this action is visible. */
FIsActionButtonVisible IsActionVisibleDelegate;
/** Can this action can be repeated if the chord used to call it is held down? */
EUIActionRepeatMode RepeatMode;
开始实践
下面开始实践,创建命令并尝试将其加入到各种编辑器扩展中。
首先,创建一个空白的插件:
插件名字TestYaksue
实践:创建命令
在\Plugins\TestYaksue\Source\TestYaksue\Private\
中创建TestYaksueCommands.h
与TestYaksueCommands.cpp
来创建命令。
TestYaksueCommands.h
:
#pragma once
#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"
class FTestYaksueCommands : public TCommands<FTestYaksueCommands>
{
public:
FTestYaksueCommands();
// TCommands<>的接口:注册命令
virtual void RegisterCommands() override;
public:
//命令A
TSharedPtr< FUICommandInfo > CommandA;
};
FTestYaksueCommands
将容纳这个插件所有的命令,当前只有一个命令CommandA
。
TestYaksueCommands.cpp
:
#include "TestYaksueCommands.h"
#define LOCTEXT_NAMESPACE "FTestYaksueModule"
FTestYaksueCommands::FTestYaksueCommands() : TCommands<FTestYaksueCommands>(
"TestYaksueCommands",
NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin"),
NAME_None,
FName(*FString("todo")))
{
}
void FTestYaksueCommands::RegisterCommands()
{
UI_COMMAND(CommandA, "TestYaksueCommandA", "Execute TestYaksue CommandA", EUserInterfaceActionType::Button, FInputChord());
}
#undef LOCTEXT_NAMESPACE
构造函数中将调用基类的构造函数:
- 参数1:const FName InContextName。现在是
"TestYaksueCommands"
- 参数2:const FText& InContextDesc。现在是
NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin")
- 参数3:const FName InContextParent。现在是
NAME_None
- 参数4:const FName InStyleSetName。这里留意,他和图标有关,现在暂时是
FName(*FString("todo"))
,如果需要图标,则这里需要改动。
RegisterCommands
中将使用UI_COMMAND宏
注册自己所有命令。留意最后一个参数,现在是FInputGesture()
,它和快捷键有关。
TestYaksue.cpp
中有这个插件的模块的启动与关闭函数。
首先
#include"TestYaksueCommands.h"
在模块的启动函数中:
FTestYaksueCommands::Register();
Register
是TCommands
的接口。用来注册命令,通常在模块的启动函数中调用。观察它的实现可看到内部调用了RegisterCommands
函数。
/** Use this method to register commands. Usually done in StartupModule(). */
FORCENOINLINE static void Register()
在模块的关闭函数中:
FTestYaksueCommands::Unregister();
Unregister
是TCommands
的接口。负责清理所有和这组命令相关的资源,通常在模块的关闭函数中被调用。
/** Use this method to clean up any resources used by the command set. Usually done in ShutdownModule() */
FORCENOINLINE static void Unregister()
实践:为命令映射具体的操作
在FTestYaksueModule
中声明一个函数用来指定CommandA
所对应的操作:
//CommandA所对应的操作
void CommandAAction();
具体做的就是弹出一个对话框
void FTestYaksueModule::CommandAAction()
{
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Execute CommandA"));
}
而“映射”操作需要由FUICommandList
完成。
为此,可以在FTestYaksueModule
中创建一个新的FUICommandList
:
TSharedPtr<class FUICommandList> PluginCommandList;
然后在模块的启动函数中创建它并映射命令:
//创建UICommandList
PluginCommandList = MakeShareable(new FUICommandList);
//为命令映射操作
PluginCommandList->MapAction(
FTestYaksueCommands::Get().CommandA,
FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
FCanExecuteAction());
实践:扩展关卡编辑器的菜单与工具栏
为了扩展关卡编辑器(LevelEditor),需要
#include "LevelEditor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
在模块的启动函数中:
//获得关卡编辑器模块
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
//扩展关卡编辑器的菜单
{
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension("WindowLayout"
, EExtensionHook::After
, PluginCommandList
, FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& Builder)
{
Builder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
}));
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}
//扩展关卡编辑器的工具栏
{
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Settings"
, EExtensionHook::After
, PluginCommandList
, FToolBarExtensionDelegate::CreateLambda([](FToolBarBuilder& Builder)
{
Builder.AddToolBarButton(FTestYaksueCommands::Get().CommandA);
}));
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}
可以在关卡编辑器的菜单中看到新扩展的命令。
也可以在关卡编辑器的工具栏中看到新的命令:
实践:快捷键
首先,需要在模块的PrivateDependencyModuleNames
中添加:"InputCore"
在使用UI_COMMAND宏
创建命令时,最后一个参数是快捷键,例如:
UI_COMMAND(CommandA, "TestYaksueCommandA", "Execute TestYaksue CommandA", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::Z));
将表示Shift + Alt+Z是这个命令的快捷键。
不过,光是这样是没有效果的,没有任何情况可以用这个快捷键触发命令。为了使这个快捷键有效,需要将这个命令使用某个“特殊”的FUICommandList
中映射。
例如:
//将CommandA添加入关卡编辑器的GlobalLevelEditorActions中,这样可以触发快捷键
{
TSharedRef<FUICommandList> LevelEditorCommandList = LevelEditorModule.GetGlobalLevelEditorActions();
LevelEditorCommandList->MapAction(
FTestYaksueCommands::Get().CommandA,
FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
FCanExecuteAction());
}
LevelEditorModule.GetGlobalLevelEditorActions
将获得关卡编辑器的FUICommandList
,使用其来映射命令可以在关卡编辑器中触发这个命令
也就是说,关卡编辑器的FUICommandList
将会在关卡编辑器内检测是否有快捷键。而之前在【实践:为命令映射具体的操作】中自己创建的FUICommandList
并没有额外的检测快捷键是否触发的机制,所以没有任何快捷键效果。
除了关卡编辑器,“内容浏览器”也有类似的FUICommandList
。它在触发命令的时候还可以知道所选择的资源是哪些。
为了实验,可以再创建一个CommandB
:
//命令B
TSharedPtr< FUICommandInfo > CommandB;
UI_COMMAND(CommandB, "TestYaksueCommandB", "Execute TestYaksue CommandB", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::X));
void FTestYaksueModule::CommandBAction(FOnContentBrowserGetSelection GetSelectionDelegate)
{
//获得当前选择的资源
TArray<FAssetData> SelectedAssets;
TArray<FString> SelectedPaths;
if (GetSelectionDelegate.IsBound())
GetSelectionDelegate.Execute(SelectedAssets, SelectedPaths);
//要显示的信息:
FString Message = "Execute CommandB:";
for (auto ad : SelectedAssets)
Message += ad.GetAsset()->GetName()+" ";
//显示对话框
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message));
}
然后先
#include "ContentBrowserModule.h"
再在模块的启动函数中:
//获得内容浏览器模块
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
//加入内容浏览器的命令
TArray<FContentBrowserCommandExtender>& CBCommandExtenderDelegates = ContentBrowserModule.GetAllContentBrowserCommandExtenders();
CBCommandExtenderDelegates.Add(FContentBrowserCommandExtender::CreateLambda([this](TSharedRef<FUICommandList> CommandList, FOnContentBrowserGetSelection GetSelectionDelegate)
{
CommandList->MapAction(FTestYaksueCommands::Get().CommandB,
FExecuteAction::CreateLambda([this, GetSelectionDelegate]
{
CommandBAction(GetSelectionDelegate);
})
);
}));
现在,CommandB
已经可以由快捷键触发,但是它不能在关卡编辑器中触发,只能在内容浏览器中触发:
实践:其他菜单拓展
UE4有多处菜单都可以拓展,例如:
内容浏览器中的Asset右键菜单
//增加内容浏览器中Asset右键菜单
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBAssetMenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
CBAssetMenuExtenderDelegates.Add(FContentBrowserMenuExtender_SelectedAssets::CreateLambda([](const TArray<FAssetData>& SelectedAssets)
{
TSharedRef<FExtender> Extender(new FExtender());
Extender->AddMenuExtension(
"AssetContextReferences",
EExtensionHook::After,
nullptr,
FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandB);
}));
return Extender;
}));
注意AddMenuExtension
时,"AssetContextReferences"
指定了命令将要添加到的分栏位置,而nullptr
参数位置本来应该是一个FUICommandList
,现在设置为空。是因为ContentBrowserModule
已经拥有一个FUICommandList
了,它在上一步中为CommandB
映射过操作了。
Asset编辑器中的菜单
需要首先在PrivateDependencyModuleNames
中添加"UnrealEd"
然后插件模块启动函数中:
//增加Asset编辑器中的菜单
TArray<FAssetEditorExtender>& AssetEditorMenuExtenderDelegates = FAssetEditorToolkit::GetSharedMenuExtensibilityManager()->GetExtenderDelegates();
AssetEditorMenuExtenderDelegates.Add(FAssetEditorExtender::CreateLambda([this](const TSharedRef<FUICommandList> CommandList, const TArray<UObject*> ContextSensitiveObjects)
{
//映射操作
CommandList->MapAction(
FTestYaksueCommands::Get().CommandA,
FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
FCanExecuteAction());
//添加菜单扩展
TSharedRef<FExtender> Extender(new FExtender());
Extender->AddMenuExtension(
"FindInContentBrowser",
EExtensionHook::After,
CommandList,
FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
}));
return Extender;
}));
实践:图标
先制作两个图标到插件的Resources目录中
需要向PrivateDependencyModuleNames
中添加"Projects"
,因为其中含有IPluginManager
可以帮助找到插件的目录在哪。
TestYaksueStyle.h
:
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
class FTestYaksueStyle
{
public:
//初始化,启动模块时调用
static void Initialize();
//关闭模块时调用
static void Shutdown();
/** reloads textures used by slate renderer */
static void ReloadTextures();
//返回这个style的名字
static FName GetStyleSetName();
private:
//创建一个实例
static TSharedRef< class FSlateStyleSet > Create();
private:
//style实例
static TSharedPtr< class FSlateStyleSet > StyleInstance;
};
TestYaksueStyle.cpp
:
#include "TestYaksueStyle.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/SlateStyleRegistry.h"
#include "Slate/SlateGameResources.h"
#include "Interfaces/IPluginManager.h"
TSharedPtr< FSlateStyleSet > FTestYaksueStyle::StyleInstance = NULL;
void FTestYaksueStyle::Initialize()
{
if (!StyleInstance.IsValid())
{
StyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
}
}
void FTestYaksueStyle::Shutdown()
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
ensure(StyleInstance.IsUnique());
StyleInstance.Reset();
}
FName FTestYaksueStyle::GetStyleSetName()
{
return TEXT("TestYaksueStyle");
}
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
const FVector2D Icon40x40(40.0f, 40.0f);
TSharedRef< FSlateStyleSet > FTestYaksueStyle::Create()
{
//创建一个新的Style实例:
TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet(GetStyleSetName()));
//设置资源目录,为本插件的Resources目录
Style->SetContentRoot(IPluginManager::Get().FindPlugin("TestYaksue")->GetBaseDir() / TEXT("Resources"));
//注册图标:
Style->Set("TestYaksueCommands.CommandA", new IMAGE_BRUSH(TEXT("PicA"), Icon40x40));
Style->Set("TestYaksueCommands.CommandB", new IMAGE_BRUSH(TEXT("PicB"), Icon40x40));
return Style;
}
#undef IMAGE_BRUSH
void FTestYaksueStyle::ReloadTextures()
{
if (FSlateApplication::IsInitialized())
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}
在FTestYaksueCommands
的构造函数中将这个Style的名字传进来
FTestYaksueCommands::FTestYaksueCommands() : TCommands<FTestYaksueCommands>(
"TestYaksueCommands",
NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin"),
NAME_None,
FTestYaksueStyle::GetStyleSetName())
在模块的启动函数中:
FTestYaksueStyle::Initialize();
FTestYaksueStyle::ReloadTextures();
最后可以看到图标:
最终的代码
TestYaksue.Build.cs
:
using UnrealBuildTool;
public class TestYaksue : ModuleRules
{
public TestYaksue(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"InputCore",
"UnrealEd",
"Projects",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
TestYaksueCommands.h
:
#pragma once
#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"
class FTestYaksueCommands : public TCommands<FTestYaksueCommands>
{
public:
FTestYaksueCommands();
// TCommands<>的接口:注册命令
virtual void RegisterCommands() override;
public:
//命令A
TSharedPtr< FUICommandInfo > CommandA;
//命令B
TSharedPtr< FUICommandInfo > CommandB;
};
TestYaksueCommands.cpp
:
#include "TestYaksueCommands.h"
#include"TestYaksueStyle.h"
#define LOCTEXT_NAMESPACE "FTestYaksueModule"
FTestYaksueCommands::FTestYaksueCommands() : TCommands<FTestYaksueCommands>(
"TestYaksueCommands",
NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin"),
NAME_None,
FTestYaksueStyle::GetStyleSetName())
{
}
void FTestYaksueCommands::RegisterCommands()
{
UI_COMMAND(CommandA, "TestYaksueCommandA", "Execute TestYaksue CommandA", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::Z));
UI_COMMAND(CommandB, "TestYaksueCommandB", "Execute TestYaksue CommandB", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::X));
}
#undef LOCTEXT_NAMESPACE
TestYaksue.h
:
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include"ContentBrowserDelegates.h"
class FTestYaksueModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
//CommandA所对应的操作
void CommandAAction();
//CommandB所对应的操作
void CommandBAction(FOnContentBrowserGetSelection GetSelectionDelegate);
private:
TSharedPtr<class FUICommandList> PluginCommandList;
};
TestYaksue.cpp
:
#include "TestYaksue.h"
#include"TestYaksueCommands.h"
#include"TestYaksueStyle.h"
#include "Misc/MessageDialog.h"
#include "Framework/Commands/UICommandList.h"
#include "LevelEditor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ContentBrowserModule.h"
#define LOCTEXT_NAMESPACE "FTestYaksueModule"
void FTestYaksueModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
//初始化Style
FTestYaksueStyle::Initialize();
FTestYaksueStyle::ReloadTextures();
//注册命令:
FTestYaksueCommands::Register();
//创建UICommandList
PluginCommandList = MakeShareable(new FUICommandList);
//为命令映射操作
PluginCommandList->MapAction(
FTestYaksueCommands::Get().CommandA,
FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
FCanExecuteAction());
//获得关卡编辑器模块
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
//扩展关卡编辑器的菜单
{
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension("WindowLayout"
, EExtensionHook::After
, PluginCommandList
, FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& Builder)
{
Builder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
}));
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}
//扩展关卡编辑器的工具栏
{
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Settings"
, EExtensionHook::After
, PluginCommandList
, FToolBarExtensionDelegate::CreateLambda([](FToolBarBuilder& Builder)
{
Builder.AddToolBarButton(FTestYaksueCommands::Get().CommandA);
}));
LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}
//将CommandA添加入关卡编辑器的GlobalLevelEditorActions中,这样可以触发快捷键
{
TSharedRef<FUICommandList> LevelEditorCommandList = LevelEditorModule.GetGlobalLevelEditorActions();
LevelEditorCommandList->MapAction(
FTestYaksueCommands::Get().CommandA,
FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
FCanExecuteAction());
}
//获得内容浏览器模块
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
//加入内容浏览器的命令
TArray<FContentBrowserCommandExtender>& CBCommandExtenderDelegates = ContentBrowserModule.GetAllContentBrowserCommandExtenders();
CBCommandExtenderDelegates.Add(FContentBrowserCommandExtender::CreateLambda([this](TSharedRef<FUICommandList> CommandList, FOnContentBrowserGetSelection GetSelectionDelegate)
{
CommandList->MapAction(FTestYaksueCommands::Get().CommandB,
FExecuteAction::CreateLambda([this, GetSelectionDelegate]
{
CommandBAction(GetSelectionDelegate);
})
);
}));
//增加内容浏览器中Asset右键菜单
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBAssetMenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
CBAssetMenuExtenderDelegates.Add(FContentBrowserMenuExtender_SelectedAssets::CreateLambda([](const TArray<FAssetData>& SelectedAssets)
{
TSharedRef<FExtender> Extender(new FExtender());
Extender->AddMenuExtension(
"AssetContextReferences",
EExtensionHook::After,
nullptr,
FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandB);
}));
return Extender;
}));
//增加Asset编辑器中的菜单
TArray<FAssetEditorExtender>& AssetEditorMenuExtenderDelegates = FAssetEditorToolkit::GetSharedMenuExtensibilityManager()->GetExtenderDelegates();
AssetEditorMenuExtenderDelegates.Add(FAssetEditorExtender::CreateLambda([this](const TSharedRef<FUICommandList> CommandList, const TArray<UObject*> ContextSensitiveObjects)
{
//映射操作
CommandList->MapAction(
FTestYaksueCommands::Get().CommandA,
FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
FCanExecuteAction());
//添加菜单扩展
TSharedRef<FExtender> Extender(new FExtender());
Extender->AddMenuExtension(
"FindInContentBrowser",
EExtensionHook::After,
CommandList,
FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
}));
return Extender;
}));
}
void FTestYaksueModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
FTestYaksueCommands::Unregister();
}
void FTestYaksueModule::CommandAAction()
{
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Execute CommandA"));
}
void FTestYaksueModule::CommandBAction(FOnContentBrowserGetSelection GetSelectionDelegate)
{
//获得当前选择的资源
TArray<FAssetData> SelectedAssets;
TArray<FString> SelectedPaths;
if (GetSelectionDelegate.IsBound())
GetSelectionDelegate.Execute(SelectedAssets, SelectedPaths);
//要显示的信息:
FString Message = "Execute CommandB:";
for (auto ad : SelectedAssets)
Message += ad.GetAsset()->GetName()+" ";
//显示对话框
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message));
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FTestYaksueModule, TestYaksue)