はじめに
まず初めにStatusBar
とは何かについてですが、これはUE5
から追加された機能でエディタ下部にContentDrawer
やOutputLog
を含む様々な機能を呼び出せる、Windowsで言うところのタスクバーのようなものです。
今回はLanguageSwitcher
というプラグインで上の画像の赤枠のように、このStatusBar
を拡張してボタンなどを追加したのでその際に分かった注意点などを紹介します。
やり方
StatusBar
は基本的にはその他のメニューと変わらずUToolMenus
を使用して拡張を行います。
public LanguageSwitcher(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.NoPCHs;
#if UE_5_2_OR_LATER
IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
#endif
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"InputCore",
"Engine",
"UnrealEd",
"Projects",
"EditorStyle",
"Slate",
"SlateCore",
"ToolMenus", // <-- これ
"MainFrame",
"Localization",
"ApplicationCore",
"InternationalizationSettings",
}
);
}
まずは、UToolMenus
を使うためにToolMenus
モジュールを依存するモジュールに追加します。
UToolMenus
の詳しい使い方などはこちらの記事で分かりやすく解説されています。
void FStatusBarExtender::ExtendStatusBar(const FName& StatusBarName)
{
auto* ToolMenus = UToolMenus::Get();
if (!IsValid(ToolMenus))
{
return;
}
UToolMenu* ToolMenu = ToolMenus->ExtendMenu(StatusBarName);
if (!IsValid(ToolMenu))
{
return;
}
FToolMenuSection& ToolMenuSection = ToolMenu->AddSection(LanguageSwitcherSectionName);
ToolMenuSection.AddEntry(
FToolMenuEntry::InitToolBarButton(
FLanguageSwitcherCommands::Get().InvokeAssignedShortcut,
FText::GetEmpty()
)
)
.SetCommandList(FLanguageSwitcherCommands::Get().CommandBindings);
ToolMenuSection.AddEntry(
FToolMenuEntry::InitComboButton(
TEXT("ComboButton"),
FToolUIActionChoice(),
FNewToolMenuChoice(
FNewToolMenuDelegate::CreateStatic(&FStatusBarExtender::MakeComboButtonSubMenu)
),
FText::GetEmpty(),
LOCTEXT("ComboButtonTooltip", "You can change the language, locale, and flags to use localized stuff."),
FSlateIcon(),
true
)
)
.SetCommandList(FLanguageSwitcherCommands::Get().CommandBindings);
}
上記のコードは関数の引数で拡張するStatusBar
のメニューの名前を受け取ります。
ExtendStatusBar(TEXT("LevelEditor.StatusBar.ToolBar"));
はじめにで添付した画像のようにレベルエディタのStatusBar
を拡張するにはLevelEditor.StatusBar.ToolBar
を指定します。
ここまでは特に工夫などせずに従来通りのエディタの拡張手順で拡張することができます。
ただ、これだけではレベルエディタ以外のアセットエディタのStatusBar
にはボタンなどが追加されません。
StatusBar
はレベルエディタを含むアセットエディタそれぞれでインスタンスを保持していて、それぞれのStatusBar
に対して同様の拡張を施す必要があります。
アセットエディタのStatusBar
の拡張
StatusBar
の拡張ポイントの名前はIToolkitHost::GetStatusBarName
で取得できる名前の末尾に.ToolBar
を付けた文字列になります。
FName SLevelEditor::GetStatusBarName() const
{
static const FName LevelEditorStatusBarName = "LevelEditor.StatusBar";
return LevelEditorStatusBarName;
}
レベルエディタでは上記のようにオーバーライドされているため、先ほどの拡張ではLevelEditor.StatusBar.ToolBar
を指定しました。
FName GetStatusBarName() const override { return StatusBarName; }
レベルエディタ以外のアセットエディタでは上記のようにオーバーライドされています。
void SStandaloneAssetEditorToolkitHost::Construct( const SStandaloneAssetEditorToolkitHost::FArguments& InArgs, const TSharedPtr<FTabManager>& InTabManager, const FName InitAppName )
{
// ...
// Asset editors have non-unique names. For example every material editor is just "MaterialEditor" so the base app name plus a unique number to generate uniqueness. This number is not used across sessions and should never be saved.
StatusBarName = FName(AppName, ++StatusBarIdGenerator);
// ...
}
StatusBarName
はSStandaloneAssetEditorToolkitHost::Construct
で設定されており、コメントにもある通りアセットエディタごとの一意の名前の末尾に連番を付けた形になります。
例えば、ブループリントエディタの場合は上の画像のようにBlueprintEditorApp_[連番]
のような形になります。
これらを踏まえてアセットエディタを開く際にToolkitHost
を介してStatusBarName
を取得し、連番部分を切り取って末尾に.ToolBar
を付けた名前を指定してFStatusBarExtender::ExtendStatusBar
を呼び出す必要があります。
void FStatusBarExtender::Register()
{
ExtendStatusBar(TEXT("LevelEditor.StatusBar.ToolBar"));
IMainFrameModule::Get().OnMainFrameCreationFinished().AddStatic(&FStatusBarExtender::HandleOnMainFrameCreationFinished);
}
void FStatusBarExtender::HandleOnMainFrameCreationFinished(TSharedPtr<SWindow> InRootWindow, bool bIsNewProjectWindow)
{
if (auto* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
{
AssetEditorSubsystem->OnEditorOpeningPreWidgets().AddStatic(&FStatusBarExtender::HandleOnEditorOpeningPreWidgets);
}
}
void FStatusBarExtender::HandleOnEditorOpeningPreWidgets(const TArray<UObject*>& AssetsToEdit, IAssetEditorInstance* AssetEditorInstance)
{
if (AssetEditorInstance == nullptr)
{
return;
}
FName BuiltStatusBarName;
{
const auto* AssetEditorToolkit = static_cast<FAssetEditorToolkit*>(AssetEditorInstance);
const TSharedRef<IToolkitHost>& ToolkitHost = AssetEditorToolkit->GetToolkitHost();
const FString StatusBarName = ToolkitHost->GetStatusBarName().ToString();
FString AssetEditorName;
if (!StatusBarName.Split(TEXT("_"), &AssetEditorName, nullptr))
{
return;
}
BuiltStatusBarName = FName(AssetEditorName + TEXT(".ToolBar"));
}
ExtendStatusBar(BuiltStatusBarName);
}
アセットエディタのStatusBar
を拡張するタイミングとしては、StatusBar
の構築時に拡張ポイントのデータが無いといけないため、UAssetEditorSubsystem::OnEditorOpeningPreWidgets
を使用します。
このデリゲートはアセットエディタが開かれる際に、アセットエディタを構成するウィジェットが構築される前に呼び出されます。
レベルエディタの拡張を含めて、プラグインのモジュールのStartupModule
で呼び出されるFStatusBarExtender::Register
でデリゲートへのバインドを行いたいですが、モジュールの開始時にはまだサブシステムのインスタンスが作成されていないため、IMainFrameModule::OnMainFrameCreationFinished
を使用してエディタのメインフレームが生成された後に処理を行います。
IMainFrameModule::OnMainFrameCreationFinished
は今回のようにタイミングが早すぎて問題がある際によく使用するため覚えておくといいかもしれません。
UAssetEditorSubsystem::OnEditorOpeningPreWidgets
のバインドする関数の引数では、これから開くアセットエディタのインスタンスをIAssetEditorInstance
の型で受け取ることができますが、IAssetEditorInstance
の状態では対応するToolkitHost
を取得できないためFAssetEditorToolkit
にキャストします。
エンジンコードを見る限りではFAssetEditorToolkit
を使用せずIAssetEditorInstance
を継承して作成したアセットエディタは作れないはずなので、キャストが失敗することはありませんが、できればIAssetEditorInstance
からToolkitHost
を取得できるような形であって欲しいですね。
これで上の画像の赤枠のように、アセットエディタのStatusBar
も拡張することができました。
エンジンビルドできるなら
ここまで色々と工夫をしてStatusBar
の拡張を行いましたが、エンジン改造が可能な場合はもっと簡単に拡張することができます。
TSharedRef<SWidget> SStatusBar::MakeStatusBarToolBarWidget()
{
RegisterStatusBarMenu();
FToolMenuContext MenuContext;
RegisterSourceControlStatus();
return UToolMenus::Get()->GenerateWidget(GetToolbarName(), MenuContext);
}
RegisterSourceControlStatus
の前後に自前の拡張を行う関数なり処理なりを追加することで全てのアセットエディタでStatusBar
を拡張することができます。
おまけ
UToolMenus
の拡張で使用する拡張ポイントの名前はEditor Preferences
> Miscellaneous
> Display UI Extension Points
をtrue
にすることでエディタ上に拡張ポイントの名前が表示されます。
(今回紹介したStatusBar
を拡張する際に使用する拡張ポイントの名前はエンジンコード上から探す必要がありました...)
小技ですが、Display UI Extension Points
の値を変更した際に適用のためエディタの再起動を求められますが、再起動せずに何かしらのアセットエディタを起動することでエディタを再起動することなく変更を適用することができます。
おわりに
今回紹介した拡張を行ったLanguageSwitcher
はショートカットキーや上の画像のようにStatusBar
から言語やリージョンを切り替えることができるプラグインです。
同様にエディタ上で動作する機能で今までツールメニューに機能を呼び出す項目を追加していた物の一部はStatusBar
から呼び出せるようにした方が便利な場合があると思うので、そういった機能を作る際にこの記事が参考になると幸いです。