uprojectの整理のためのEditor Utility Widgetを作成中に知ったことの備忘録。
フォルダを選択するUIがほしいが、いわゆる Blutility には公開されていない様子。
なので、なんとなく自作してみた。
IContentBrowserSingleton::CreatePathPicker()
IContentBrowserSingleton にある CreatePathPicker を使えばよい様子。
大雑把には、
- FPathPickerConfig に必要な情報をセットする。
- FContentBrowserModule を取得し、CreatePathPicker() を呼び出す。
- Slate 製 SPathPicker が得られるので、それをダイアログウィンドウに貼り付ける。
という手順で作れた。
- FContentBrowserModule を使うので、ContentBrowserModule.h のインクルードと、ContentBrowser モジュールを .Build.cs に追加する必要がある。
- OK ボタンや Cancel ボタンはないので、Slate で自作する必要がある。
ソースコード
class ValidPath
- 存在しないパスを指定されたら、"/Game"に設定するだけのユーティリティクラス。
- UEditorAssetLibrary::DoesDirectoryExist() を使うので、EditorAssetLibrary.h のインクルードと、EditorScriptingUtilities モジュールの .Build.cs への追加が必要。
アセット操作では AssetRegistry, AssetTools, ContentBrowser などのモジュールはよく使うものの、UEditorAssetLibrary の存在を忘れがち(私は)。
困ったら、なんかもう一つあったよね?(= UEditorAssetLibrary) を思い出すべし。
※UEditorAssetLibrary は、いわゆる Blutility にも公開されている関数群。
ValidPath
namespace assetutils
{
class ValidPath
{
public:
ValidPath(const FString& InPath)
{
if (UEditorAssetLibrary::DoesDirectoryExist(InPath))
{
Path = InPath;
}
else
{
UE_LOG(LogRinderonEditor, Warning, TEXT("assetutils::ValidPath() InPath(%s) is not exist, \"/Game\" is used instead."), *InPath);
Path = TEXT("/Game");
}
}
public:
const FString& Get() const { return Path; }
private: // internal properties
FString Path;
}; // class ValidPath
} // namespace assetutils
class SDirectoryDialog
パス選択ダイアログ本体。
Slate はろくにいじったことがないので、これで合っているか不安。
- SHorizontalBox buttonsBox は、OK ボタンと Cancel ボタンを持っている。
- SVerticalBox mainBox は、上側に SPathPicker を、下側に buttonsBox を持っている。
SDirectoryDialog
// directory selection dialog (slate)
class SDirectoryDialog : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SDirectoryDialog) {}
SLATE_END_ARGS()
public:
SDirectoryDialog() {}
virtual ~SDirectoryDialog() {}
virtual void Construct(const FArguments& InArgs, const FString& InBasePath)
{
assetutils::ValidPath basePath(InBasePath);
FPathPickerConfig config;
config.DefaultPath = basePath.Get();
config.OnPathSelected = FOnPathSelected::CreateRaw(this, &SDirectoryDialog::OnPathSelected);
FContentBrowserModule& contentBrowserModule
= FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
TSharedPtr<SPathPicker> pathPicker = StaticCastSharedRef<SPathPicker>(
contentBrowserModule.Get().CreatePathPicker(config));
TSharedRef<SHorizontalBox> buttonsBox = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Bottom)
.Padding(4.0f, 3.0f)
[
SNew(SButton)
.Text(LOCTEXT("DirectoryPickerConfirmButton", "OK"))
.ContentPadding(FMargin(8.0f, 2.0f))
.IsEnabled(this, &SDirectoryDialog::IsConfirmButtonEnabled)
.OnClicked(this, &SDirectoryDialog::OnConfirmButtonClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Bottom)
.Padding(4.0f, 3.0f)
[
SNew(SButton)
.Text(LOCTEXT("DirectoryPickerCancelButton", "Cancel"))
.ContentPadding(FMargin(8.0f, 2.0f))
.OnClicked(this, &SDirectoryDialog::OnCancelButtonClicked)
];
TSharedRef<SVerticalBox> mainBox = SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(0.0f, 0.0f, 0.0f, 4.0f)
[
pathPicker.ToSharedRef()
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(0.0f)
[
buttonsBox
];
ChildSlot
[
mainBox
];
}
public: // public methods
bool HasValidResult() const { return ConfirmedFlag && (!SelectedPath.IsEmpty()); }
const FString& GetPath() const { return SelectedPath; }
private: // internal methods: utilities
void CloseDialog()
{
TSharedPtr<SWindow> window = FSlateApplication::Get().FindWidgetWindow(AsShared());
if (window.IsValid())
{
window->RequestDestroyWindow();
}
}
private: // internal methods: delegates
bool IsConfirmButtonEnabled() const { return true; }
FReply OnConfirmButtonClicked()
{
CloseDialog();
ConfirmedFlag = true;
return FReply::Handled();
}
FReply OnCancelButtonClicked()
{
CloseDialog();
ConfirmedFlag = false;
return FReply::Handled();
}
void OnPathSelected(const FString& InCurrentPath) { SelectedPath = InCurrentPath; }
private: // internal properties
FString SelectedPath;
bool ConfirmedFlag = false;
};
BrowseForFolder() 関数
ダイアログを呼び出すための Blueprint Function Library の関数。
- IMainFrameModule のために、Interfaces/IMainFrameModule.h のインクルードと、MainFrame モジュールの .Build.cs への追加が必要。
BrowseForFolder
void URinderonEditorFunctionLibrary::BrowseForFolder(const FString& InBasePath,
bool& OutSuccess, FString& OutPath)
{
TSharedRef<SWindow> window = SNew(SWindow)
.Title(LOCTEXT("BrowseForFolderTitle", "Browse Folders"))
.ClientSize(FVector2D(320.0f, 320.0f))
.SizingRule(ESizingRule::UserSized)
.SupportsMaximize(false)
.SupportsMinimize(false);
TSharedRef<SDirectoryDialog> dialog = SNew(SDirectoryDialog, InBasePath);
window->SetContent(dialog);
IMainFrameModule& mainFrameModule
= FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
const TSharedPtr<SWindow>& parentWindow = mainFrameModule.GetParentWindow();
if (parentWindow.IsValid())
{
FSlateApplication::Get().AddModalWindow(window, parentWindow.ToSharedRef());
if (dialog->HasValidResult())
{
OutSuccess = true;
OutPath = dialog->GetPath();
return;
}
}
OutSuccess = false;
OutPath.Empty();
return;
}
Editor Utility Widget からの呼び出し
非常にシンプルに使えて便利。