4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめに

まず初めにStatusBarとは何かについてですが、これはUE5から追加された機能でエディタ下部にContentDrawerOutputLogを含む様々な機能を呼び出せる、Windowsで言うところのタスクバーのようなものです。

Untitled.png

今回はLanguageSwitcherというプラグインで上の画像の赤枠のように、このStatusBarを拡張してボタンなどを追加したのでその際に分かった注意点などを紹介します。

やり方

StatusBarは基本的にはその他のメニューと変わらずUToolMenusを使用して拡張を行います。

LanguageSwitcher.Build.cs
	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の詳しい使い方などはこちらの記事で分かりやすく解説されています。

StatusBarExtender.cpp
	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のメニューの名前を受け取ります。

StatusBarExtender.cpp
	ExtendStatusBar(TEXT("LevelEditor.StatusBar.ToolBar"));

はじめにで添付した画像のようにレベルエディタのStatusBarを拡張するにはLevelEditor.StatusBar.ToolBarを指定します。

ここまでは特に工夫などせずに従来通りのエディタの拡張手順で拡張することができます。
ただ、これだけではレベルエディタ以外のアセットエディタのStatusBarにはボタンなどが追加されません。
StatusBarはレベルエディタを含むアセットエディタそれぞれでインスタンスを保持していて、それぞれのStatusBarに対して同様の拡張を施す必要があります。

アセットエディタのStatusBarの拡張

StatusBarの拡張ポイントの名前はIToolkitHost::GetStatusBarNameで取得できる名前の末尾に.ToolBarを付けた文字列になります。

SLevelEditor.cpp
FName SLevelEditor::GetStatusBarName() const
{
	static const FName LevelEditorStatusBarName = "LevelEditor.StatusBar";
	return LevelEditorStatusBarName;
}

レベルエディタでは上記のようにオーバーライドされているため、先ほどの拡張ではLevelEditor.StatusBar.ToolBarを指定しました。

SStandaloneAssetEditorToolkitHost.h
FName GetStatusBarName() const override { return StatusBarName; } 

レベルエディタ以外のアセットエディタでは上記のようにオーバーライドされています。

SStandaloneAssetEditorToolkitHost.cpp
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);

	// ...
}

StatusBarNameSStandaloneAssetEditorToolkitHost::Constructで設定されており、コメントにもある通りアセットエディタごとの一意の名前の末尾に連番を付けた形になります。

image.png

例えば、ブループリントエディタの場合は上の画像のようにBlueprintEditorApp_[連番]のような形になります。
これらを踏まえてアセットエディタを開く際にToolkitHostを介してStatusBarNameを取得し、連番部分を切り取って末尾に.ToolBarを付けた名前を指定してFStatusBarExtender::ExtendStatusBarを呼び出す必要があります。

StatusBarExtender.cpp
	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を取得できるような形であって欲しいですね。

image.png

これで上の画像の赤枠のように、アセットエディタのStatusBarも拡張することができました。

エンジンビルドできるなら

ここまで色々と工夫をしてStatusBarの拡張を行いましたが、エンジン改造が可能な場合はもっと簡単に拡張することができます。

SStatusBar.cpp
TSharedRef<SWidget> SStatusBar::MakeStatusBarToolBarWidget()
{
	RegisterStatusBarMenu();
	
	FToolMenuContext MenuContext;
	RegisterSourceControlStatus();

	return UToolMenus::Get()->GenerateWidget(GetToolbarName(), MenuContext);
}

RegisterSourceControlStatusの前後に自前の拡張を行う関数なり処理なりを追加することで全てのアセットエディタでStatusBarを拡張することができます。

おまけ

UToolMenusの拡張で使用する拡張ポイントの名前はEditor Preferences > Miscellaneous > Display UI Extension Pointstrueにすることでエディタ上に拡張ポイントの名前が表示されます。
(今回紹介したStatusBarを拡張する際に使用する拡張ポイントの名前はエンジンコード上から探す必要がありました...)

image.png

小技ですが、Display UI Extension Pointsの値を変更した際に適用のためエディタの再起動を求められますが、再起動せずに何かしらのアセットエディタを起動することでエディタを再起動することなく変更を適用することができます。

おわりに

Usage.PNG

今回紹介した拡張を行ったLanguageSwitcherはショートカットキーや上の画像のようにStatusBarから言語やリージョンを切り替えることができるプラグインです。
同様にエディタ上で動作する機能で今までツールメニューに機能を呼び出す項目を追加していた物の一部はStatusBarから呼び出せるようにした方が便利な場合があると思うので、そういった機能を作る際にこの記事が参考になると幸いです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?