// Copyright Epic Games, Inc. All Rights Reserved. #include "SMergeAssetPickerView.h" #include "Templates/SubclassOf.h" #include "Engine/Blueprint.h" #include "Widgets/SBoxPanel.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Layout/SBox.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SSplitter.h" #include "Styling/AppStyle.h" #include "AssetRegistry/AssetData.h" #include "Editor.h" #include "BlueprintMergeData.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "SBlueprintRevisionMenu.h" #include "Engine/Selection.h" #define LOCTEXT_NAMESPACE "SMergeAssetPickerView" /******************************************************************************* * SMergeAssetPickerPanel ******************************************************************************/ class SMergeAssetPickerPanel : public SCompoundWidget { DECLARE_DELEGATE_OneParam(FOnAssetChanged, const UObject*); DECLARE_DELEGATE_OneParam(FOnRevisionChanged, const FRevisionInfo&); public: SLATE_BEGIN_ARGS(SMergeAssetPickerPanel) : _AssetClass(UBlueprint::StaticClass()) , _SelectedAsset(nullptr) , _RevisionInfo(FRevisionInfo::InvalidRevision()) {} SLATE_ARGUMENT(TSubclassOf, AssetClass) SLATE_ARGUMENT(const UBlueprint*, SelectedAsset) SLATE_ARGUMENT(FRevisionInfo, RevisionInfo) SLATE_EVENT(FOnAssetChanged, OnAssetChanged) SLATE_EVENT(FOnRevisionChanged, OnRevisionChanged) SLATE_END_ARGS() /** */ SMergeAssetPickerPanel() : SelectedAsset(nullptr) , SelectedRevision(FRevisionInfo::InvalidRevision()) {} /** * Builds the slate UI for SMergeAssetPickerPanel (as well as caches off any * important data that was supplied as part of the FArguments struct). * * @param Args The set of slate arguments that are used to customize this panel. */ void Construct(const FArguments Args); /** * Prior to launch, sometimes temp assets are created for the merge-tool * (old revisions are dumped into the temp directory, etc.). This function * determines if the currently selected asset (for this panel) is one of * those temporary asset files. * * @return True if the selected asset is a temporary one, other wise false (if it is a legit asset, or if one isn't selected). */ bool IsTempAssetSelected() const; private: TSharedRef MakeAssetPicker(); FText GetAssetPickerTextValue() const; FReply OnUseSelectedAssetClick(); void ResetSelectedAsset(const UObject* NewSelection); bool IsBrowseButtonEnabled() const; FReply OnBrowseToPickedAsset(); TSharedRef MakeAssetPickerMenu(); void OnAssetSelected(FAssetData const& AssetData); TSharedRef MakeRevisionPicker(); FText GetRevisionTextValue() const; TSharedRef MakeRevisionPickerMenu(); void OnRevisionSelected(FRevisionInfo const& RevisionInfo); TSubclassOf AssetClass; UBlueprint const* SelectedAsset; FOnAssetChanged OnAssetChanged; FRevisionInfo SelectedRevision; FOnRevisionChanged OnRevisionChanged; TSharedPtr AssetPicker; }; //------------------------------------------------------------------------------ void SMergeAssetPickerPanel::Construct(const FArguments InArgs) { AssetClass = InArgs._AssetClass; SelectedAsset = InArgs._SelectedAsset; SelectedRevision = InArgs._RevisionInfo; OnAssetChanged = InArgs._OnAssetChanged; OnRevisionChanged = InArgs._OnRevisionChanged; ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ SNew(SBox) .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ SNew(SVerticalBox) + SVerticalBox::Slot() .HAlign(HAlign_Left) .AutoHeight() .Padding(0, 2) [ MakeAssetPicker() ] + SVerticalBox::Slot() .HAlign(HAlign_Left) .AutoHeight() .Padding(0, 2) [ MakeRevisionPicker() ] ] ] ]; } //------------------------------------------------------------------------------ bool SMergeAssetPickerPanel::IsTempAssetSelected() const { return (SelectedAsset != nullptr) && (!SelectedAsset->IsAsset() ||(SelectedAsset->GetOutermost() == GetTransientPackage()) || SelectedAsset->GetPathName().StartsWith("/Temp/")); } //------------------------------------------------------------------------------ TSharedRef SMergeAssetPickerPanel::MakeAssetPicker() { TSharedPtr BrowseButton; SAssignNew(BrowseButton, SButton) .ButtonStyle(FAppStyle::Get(), "NoBorder") .OnClicked(FOnClicked::CreateSP(this, &SMergeAssetPickerPanel::OnBrowseToPickedAsset)) .ContentPadding(0) .ToolTipText(NSLOCTEXT("GraphEditor", "ObjectGraphPin_Browse", "Show the selected asset in the content browser.")) [ SNew(SImage) .Image(FAppStyle::GetBrush(TEXT("Icons.Search"))) ]; BrowseButton->SetEnabled(TAttribute(this, &SMergeAssetPickerPanel::IsBrowseButtonEnabled)); return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(2, 0) .MaxWidth(150.0f) [ SAssignNew(AssetPicker, SComboButton) .ButtonStyle(FAppStyle::Get(), "PropertyEditor.AssetComboStyle") .ContentPadding(FMargin(2, 2, 2, 1)) .OnGetMenuContent(this, &SMergeAssetPickerPanel::MakeAssetPickerMenu) .ButtonContent() [ SNew(SBox).WidthOverride(150.f) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "PropertyEditor.AssetClass") .Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont")) .Text(this, &SMergeAssetPickerPanel::GetAssetPickerTextValue) ] ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(1, 0) .VAlign(VAlign_Center) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "NoBorder") .OnClicked(FOnClicked::CreateSP(this, &SMergeAssetPickerPanel::OnUseSelectedAssetClick)) .ContentPadding(1.f) .ToolTipText(NSLOCTEXT("GraphEditor", "ObjectGraphPin_Use", "Use content browser selection.")) [ SNew(SImage) .Image(FAppStyle::GetBrush(TEXT("Icons.CircleArrowLeft"))) ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(1, 0) .VAlign(VAlign_Center) [ BrowseButton.ToSharedRef() ]; } //------------------------------------------------------------------------------ FText SMergeAssetPickerPanel::GetAssetPickerTextValue() const { FText AssetPickerTitle = LOCTEXT("SelectAsset", "Select an Asset..."); if (SelectedAsset != nullptr) { if (UClass* BlueprintClass = SelectedAsset->GeneratedClass) { AssetPickerTitle = BlueprintClass->GetDisplayNameText(); } else { AssetPickerTitle = FText::FromString(SelectedAsset->GetName()); } } return AssetPickerTitle; } //------------------------------------------------------------------------------ FReply SMergeAssetPickerPanel::OnUseSelectedAssetClick() { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); UObject* SelectedObject = GEditor->GetSelectedObjects()->GetTop(AssetClass); if (SelectedObject != nullptr) { ResetSelectedAsset(SelectedObject); } return FReply::Handled(); } //------------------------------------------------------------------------------ void SMergeAssetPickerPanel::ResetSelectedAsset(const UObject* NewSelection) { SelectedAsset = Cast(NewSelection); OnAssetChanged.ExecuteIfBound(SelectedAsset); // reset the revision (to current) SelectedRevision = FRevisionInfo::InvalidRevision(); OnRevisionChanged.ExecuteIfBound(SelectedRevision); } //------------------------------------------------------------------------------ bool SMergeAssetPickerPanel::IsBrowseButtonEnabled() const { return (SelectedAsset != nullptr); } //------------------------------------------------------------------------------ FReply SMergeAssetPickerPanel::OnBrowseToPickedAsset() { if (SelectedAsset != nullptr) { TArray BrowseObjects; // @TODO: SyncBrowserToObjects() should really take an array of const objects BrowseObjects.Add(const_cast(SelectedAsset)); GEditor->SyncBrowserToObjects(BrowseObjects); } return FReply::Handled(); } //------------------------------------------------------------------------------ TSharedRef SMergeAssetPickerPanel::MakeAssetPickerMenu() { FAssetPickerConfig AssetPickerConfig; AssetPickerConfig.Filter.ClassPaths.Add(AssetClass->GetClassPathName()); AssetPickerConfig.bAllowNullSelection = true; AssetPickerConfig.Filter.bRecursiveClasses = true; AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SMergeAssetPickerPanel::OnAssetSelected); AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; AssetPickerConfig.bAllowDragging = false; FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); return SNew(SBox) .WidthOverride(300) .HeightOverride(300) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) [ ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig) ] ]; } //------------------------------------------------------------------------------ void SMergeAssetPickerPanel::OnAssetSelected(FAssetData const& AssetData) { AssetPicker->SetIsOpen(false); ResetSelectedAsset(AssetData.GetAsset()); } //------------------------------------------------------------------------------ TSharedRef SMergeAssetPickerPanel::MakeRevisionPicker() { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(2, 0) .MaxWidth(150.0f) [ SAssignNew(AssetPicker, SComboButton) .ButtonStyle(FAppStyle::Get(), "PropertyEditor.AssetComboStyle") .ContentPadding(FMargin(2, 2, 2, 1)) .OnGetMenuContent(this, &SMergeAssetPickerPanel::MakeRevisionPickerMenu) .ButtonContent() [ SNew(SBox).WidthOverride(150.f) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "PropertyEditor.AssetClass") .Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont")) .Text(this, &SMergeAssetPickerPanel::GetRevisionTextValue) ] ] ]; } //------------------------------------------------------------------------------ FText SMergeAssetPickerPanel::GetRevisionTextValue() const { FText RevisionText = LOCTEXT("UnknownRevision", "Unknown Revision"); if (SelectedAsset == nullptr) { RevisionText = LOCTEXT("PickRevision", "Pick a Revision..."); } else if (!SelectedRevision.Revision.IsEmpty()) { RevisionText = FText::Format(LOCTEXT("RevisionNum", "Revision {0}"), FText::FromString(SelectedRevision.Revision)); } else if (!IsTempAssetSelected()) { RevisionText = LOCTEXT("LocalRevision", "Local Copy"); } else { RevisionText = LOCTEXT("UnknownRevision", "Unknown Revision"); } return RevisionText; } //------------------------------------------------------------------------------ TSharedRef SMergeAssetPickerPanel::MakeRevisionPickerMenu() { if (SelectedAsset == nullptr) { return SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) [ SNew(STextBlock).Text(LOCTEXT("SelectAssetFirst", "Select an asset first.")) ]; } else if (!IsTempAssetSelected()) { return SNew(SBlueprintRevisionMenu, SelectedAsset) .bIncludeLocalRevision(true) .OnRevisionSelected(this, &SMergeAssetPickerPanel::OnRevisionSelected); } else { FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection =*/true, /*CommandList =*/nullptr); MenuBuilder.BeginSection("RevisionSection", LOCTEXT("Revisions", "Revisions")); { FText const RevisionTooltip = LOCTEXT("UnknownRevisionTip", "The selected asset must be a temporary one created by the merge-tool (with this preordained revision)."); MenuBuilder.AddMenuEntry(TAttribute(this, &SMergeAssetPickerPanel::GetRevisionTextValue), RevisionTooltip, FSlateIcon(), FUIAction()); } MenuBuilder.EndSection(); return SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) [ MenuBuilder.MakeWidget() ]; } } //------------------------------------------------------------------------------ void SMergeAssetPickerPanel::OnRevisionSelected(FRevisionInfo const& RevisionInfo) { SelectedRevision = RevisionInfo; OnRevisionChanged.ExecuteIfBound(RevisionInfo); } /******************************************************************************* * SMergeAssetPickerView ******************************************************************************/ //------------------------------------------------------------------------------ void SMergeAssetPickerView::Construct(const FArguments InArgs, const FBlueprintMergeData& InData) { MergeAssetSet[EMergeAssetId::MergeRemote].AssetName = InData.BlueprintRemote->GetOutermost()->GetName(); MergeAssetSet[EMergeAssetId::MergeRemote].Revision = InData.RevisionRemote; MergeAssetSet[EMergeAssetId::MergeBase].AssetName = InData.BlueprintBase->GetOutermost()->GetName(); MergeAssetSet[EMergeAssetId::MergeBase].Revision = InData.RevisionBase; MergeAssetSet[EMergeAssetId::MergeLocal].AssetName = InData.BlueprintLocal->GetOutermost()->GetName(); MergeAssetSet[EMergeAssetId::MergeLocal].Revision = InData.RevisionLocal; OnAssetChanged = InArgs._OnAssetChanged; ChildSlot [ SNew(SSplitter) + SSplitter::Slot() [ SNew(SMergeAssetPickerPanel) .SelectedAsset(InData.BlueprintRemote) .RevisionInfo(InData.RevisionRemote) .OnRevisionChanged(this, &SMergeAssetPickerView::HandleRevisionChange, EMergeAssetId::MergeRemote) .OnAssetChanged(this, &SMergeAssetPickerView::HandleAssetChange, EMergeAssetId::MergeRemote) ] + SSplitter::Slot() [ SNew(SMergeAssetPickerPanel) .SelectedAsset(InData.BlueprintBase) .RevisionInfo(InData.RevisionBase) .OnRevisionChanged(this, &SMergeAssetPickerView::HandleRevisionChange, EMergeAssetId::MergeBase) .OnAssetChanged(this, &SMergeAssetPickerView::HandleAssetChange, EMergeAssetId::MergeBase) ] + SSplitter::Slot() [ SNew(SMergeAssetPickerPanel) .SelectedAsset(InData.BlueprintLocal) .RevisionInfo(InData.RevisionLocal) .OnRevisionChanged(this, &SMergeAssetPickerView::HandleRevisionChange, EMergeAssetId::MergeLocal) .OnAssetChanged(this, &SMergeAssetPickerView::HandleAssetChange, EMergeAssetId::MergeLocal) ] ]; } //------------------------------------------------------------------------------ void SMergeAssetPickerView::HandleAssetChange(const UObject* NewAsset, EMergeAssetId::Type PanelId) { if (NewAsset == nullptr) { MergeAssetSet[PanelId].AssetName.Empty(); } else { MergeAssetSet[PanelId].AssetName = NewAsset->GetOutermost()->GetName(); } OnAssetChanged.ExecuteIfBound(PanelId, MergeAssetSet[PanelId]); } //------------------------------------------------------------------------------ void SMergeAssetPickerView::HandleRevisionChange(const FRevisionInfo& NewRevision, EMergeAssetId::Type PanelId) { MergeAssetSet[PanelId].Revision = NewRevision; OnAssetChanged.ExecuteIfBound(PanelId, MergeAssetSet[PanelId]); } #undef LOCTEXT_NAMESPACE