はじめに
まず初めに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から呼び出せるようにした方が便利な場合があると思うので、そういった機能を作る際にこの記事が参考になると幸いです。




