Files
UnrealEngine/Engine/Source/Editor/Persona/Private/SSkeletonSlotNames.cpp
2025-05-18 13:04:45 +08:00

1050 lines
35 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SSkeletonSlotNames.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Misc/ScopedSlowTask.h"
#include "Framework/MultiBox/MultiBoxDefs.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Animation/Skeleton.h"
#include "Styling/AppStyle.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Animation/AnimMontage.h"
#include "FileHelpers.h"
#include "SSlotNameReferenceWindow.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ScopedTransaction.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Input/STextEntryPopup.h"
#include "Animation/AnimBlueprint.h"
#include "AnimGraphNode_Slot.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Interfaces/IMainFrameModule.h"
#include "IEditableSkeleton.h"
#include "TabSpawners.h"
#define LOCTEXT_NAMESPACE "SkeletonSlotNames"
static const FName ColumnId_SlotNameLabel( "SlotName" );
//////////////////////////////////////////////////////////////////////////
// SMorphTargetListRow
typedef TSharedPtr< FDisplayedSlotNameInfo > FDisplayedSlotNameInfoPtr;
class SSlotNameListRow
: public SMultiColumnTableRow< FDisplayedSlotNameInfoPtr >
{
public:
SLATE_BEGIN_ARGS( SSlotNameListRow ) {}
/** The item for this row **/
SLATE_ARGUMENT( FDisplayedSlotNameInfoPtr, Item )
/* Widget used to display the list of morph targets */
SLATE_ARGUMENT( TSharedPtr<SSkeletonSlotNames>, SlotNameListView )
SLATE_END_ARGS()
void Construct( const FArguments& InArgs, const TSharedRef<STableViewBase>& OwnerTableView );
/** Overridden from SMultiColumnTableRow. Generates a widget for this column of the tree row. */
virtual TSharedRef<SWidget> GenerateWidgetForColumn( const FName& ColumnName ) override;
private:
/** Widget used to display the list of slot name */
TSharedPtr<SSkeletonSlotNames> SlotNameListView;
/** The notify being displayed by this row */
FDisplayedSlotNameInfoPtr Item;
};
void SSlotNameListRow::Construct( const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView )
{
Item = InArgs._Item;
SlotNameListView = InArgs._SlotNameListView;
check( Item.IsValid() );
SMultiColumnTableRow< FDisplayedSlotNameInfoPtr >::Construct( FSuperRowType::FArguments(), InOwnerTableView );
}
TSharedRef< SWidget > SSlotNameListRow::GenerateWidgetForColumn( const FName& ColumnName )
{
check( ColumnName == ColumnId_SlotNameLabel );
// Items can be either Slots or Groups.
FText ItemText = Item->bIsGroupItem ? FText::Format(LOCTEXT("AnimSlotManagerGroupItem", "(Group) {0}"), FText::FromName(Item->Name))
: FText::Format(LOCTEXT("AnimSlotManagerSlotItem", "(Slot) {0}"), FText::FromName(Item->Name));
return
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SExpanderArrow, SharedThis(this))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(ItemText)
];
}
/////////////////////////////////////////////////////
// FSkeletonSlotNamesSummoner
FSkeletonSlotNamesSummoner::FSkeletonSlotNamesSummoner(TSharedPtr<class FAssetEditorToolkit> InHostingApp, const TSharedRef<IEditableSkeleton>& InEditableSkeleton, FOnObjectSelected InOnObjectSelected)
: FWorkflowTabFactory(FPersonaTabs::SkeletonSlotNamesID, InHostingApp)
, EditableSkeleton(InEditableSkeleton)
, OnObjectSelected(InOnObjectSelected)
{
TabLabel = LOCTEXT("AnimSlotManagerTabTitle", "Anim Slot Manager");
TabIcon = FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.Tabs.AnimSlotManager");
EnableTabPadding();
bIsSingleton = true;
ViewMenuDescription = LOCTEXT("SkeletonSlotNamesMenu", "Anim Slots");
ViewMenuTooltip = LOCTEXT("SkeletonSlotNames_ToolTip", "Manage Skeleton's Slots and Groups.");
}
TSharedRef<SWidget> FSkeletonSlotNamesSummoner::CreateTabBody(const FWorkflowTabSpawnInfo& Info) const
{
return SNew(SSkeletonSlotNames, EditableSkeleton.Pin().ToSharedRef())
.OnObjectSelected(OnObjectSelected);
}
/////////////////////////////////////////////////////
// SSkeletonSlotNames
void SSkeletonSlotNames::Construct(const FArguments& InArgs, const TSharedRef<IEditableSkeleton>& InEditableSkeleton)
{
EditableSkeleton = InEditableSkeleton;
OnObjectSelected = InArgs._OnObjectSelected;
InEditableSkeleton->RegisterOnSlotsChanged(FSimpleMulticastDelegate::FDelegate::CreateSP(this, &SSkeletonSlotNames::RefreshSlotNameListWithFilter));
// Toolbar
FToolBarBuilder ToolbarBuilder(TSharedPtr< FUICommandList >(), FMultiBoxCustomization::None);
// Save USkeleton
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnSaveSkeleton))
, NAME_None
, LOCTEXT("AnimSlotManagerToolbarSaveLabel", "Save")
, LOCTEXT("AnimSlotManagerToolbarSaveTooltip", "Saves changes into Skeleton asset")
, FSlateIcon(FAppStyle::GetAppStyleSetName(), "AnimSlotManager.SaveSkeleton")
);
ToolbarBuilder.AddSeparator();
// Add Slot
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnAddSlot))
, NAME_None
, LOCTEXT("AnimSlotManagerToolbarAddSlotLabel", "Add Slot")
, LOCTEXT("AnimSlotManagerToolbarAddSlotTooltip", "Create a new unique Slot name")
, FSlateIcon(FAppStyle::GetAppStyleSetName(), "AnimSlotManager.AddSlot")
);
// Add Group
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &SSkeletonSlotNames::OnAddGroup))
, NAME_None
, LOCTEXT("AnimSlotManagerToolbarAddGroupLabel", "Add Group")
, LOCTEXT("AnimSlotManagerToolbarAddGroupTooltip", "Create a new unique Group name")
, FSlateIcon(FAppStyle::GetAppStyleSetName(), "AnimSlotManager.AddGroup")
);
this->ChildSlot
[
SNew( SVerticalBox )
+ SVerticalBox::Slot()
.AutoHeight()
[
ToolbarBuilder.MakeWidget()
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding( FMargin( 0.0f, 0.0f, 0.0f, 4.0f ) )
[
SAssignNew( NameFilterBox, SSearchBox )
.SelectAllTextWhenFocused( true )
.OnTextChanged( this, &SSkeletonSlotNames::OnFilterTextChanged )
.OnTextCommitted( this, &SSkeletonSlotNames::OnFilterTextCommitted )
.HintText( LOCTEXT( "AnimSlotManagerSlotNameSearchBoxHint", "Slot name filter...") )
]
+ SVerticalBox::Slot()
.FillHeight( 1.0f ) // This is required to make the scrollbar work, as content overflows Slate containers by default
[
SAssignNew( SlotNameListView, SSlotNameListType )
.TreeItemsSource(&NotifyList)
.OnGenerateRow( this, &SSkeletonSlotNames::GenerateNotifyRow )
.OnGetChildren(this, &SSkeletonSlotNames::GetChildrenForInfo)
.OnContextMenuOpening( this, &SSkeletonSlotNames::OnGetContextMenuContent )
.SelectionMode(ESelectionMode::Single)
.OnSelectionChanged( this, &SSkeletonSlotNames::OnNotifySelectionChanged )
.HeaderRow
(
SNew( SHeaderRow )
+ SHeaderRow::Column( ColumnId_SlotNameLabel )
.DefaultLabel( LOCTEXT( "SlotNameNameLabel", "Slot Name" ) )
)
]
];
CreateSlotNameList();
}
void SSkeletonSlotNames::OnFilterTextChanged( const FText& SearchText )
{
FilterText = SearchText;
RefreshSlotNameListWithFilter();
}
void SSkeletonSlotNames::OnFilterTextCommitted( const FText& SearchText, ETextCommit::Type CommitInfo )
{
// Just do the same as if the user typed in the box
OnFilterTextChanged( SearchText );
}
TSharedRef<ITableRow> SSkeletonSlotNames::GenerateNotifyRow(TSharedPtr<FDisplayedSlotNameInfo> InInfo, const TSharedRef<STableViewBase>& OwnerTable)
{
check( InInfo.IsValid() );
return
SNew( SSlotNameListRow, OwnerTable )
.Item( InInfo )
.SlotNameListView( SharedThis( this ) );
}
void SSkeletonSlotNames::GetChildrenForInfo(TSharedPtr<FDisplayedSlotNameInfo> InInfo, TArray< TSharedPtr<FDisplayedSlotNameInfo> >& OutChildren)
{
check(InInfo.IsValid());
OutChildren = InInfo->Children;
}
TSharedPtr<SWidget> SSkeletonSlotNames::OnGetContextMenuContent() const
{
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, NULL);
TArray< TSharedPtr< FDisplayedSlotNameInfo > > SelectedItems = SlotNameListView.Get()->GetSelectedItems();
bool bHasSelectedItem = (SelectedItems.Num() > 0);
bool bShowGroupItem = bHasSelectedItem && SelectedItems[0].Get()->bIsGroupItem;
bool bShowSlotItem = bHasSelectedItem && !SelectedItems[0].Get()->bIsGroupItem;
if (bShowGroupItem)
{
TSharedPtr<FDisplayedSlotNameInfo> SelectedItemPtr = SelectedItems[0];
MenuBuilder.BeginSection("SlotManagerSlotGroupActions", LOCTEXT("SlotManagerSlotGroupActions", "Slot Group Actions"));
// Delete Slot Group
{
FDisplayedSlotNameInfo* SlotInfo = SelectedItemPtr.Get();
FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::OnDeleteSlotGroup, SlotInfo->Name));
Action.CanExecuteAction.BindSP(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::CanDeleteSlotGroup, SlotInfo->Name);
const FText Label = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotGroupLabel", "Delete Slot Group");
const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotGroupTooltip", "Delete this slot group.");
MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action);
}
MenuBuilder.EndSection();
}
else if (bShowSlotItem)
{
TSharedPtr<FDisplayedSlotNameInfo> SelectedItemPtr = SelectedItems[0];
MenuBuilder.BeginSection("SlotManagerSlotActions", LOCTEXT("SlotManagerSlotActions", "Slot Actions"));
// Set Slot Group
{
MenuBuilder.AddSubMenu(
FText::Format(LOCTEXT("ContextMenuSetSlotGroupLabel", "Set Slot {0} Group to"), FText::FromName(SelectedItems[0].Get()->Name)),
FText::Format(LOCTEXT("ContextMenuSetSlotGroupToolTip", "Set Slot {0} Group"), FText::FromName(SelectedItems[0].Get()->Name)),
FNewMenuDelegate::CreateRaw(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::FillSetSlotGroupSubMenu));
}
// Rename Slot
{
FDisplayedSlotNameInfo* SlotInfo = SelectedItemPtr.Get();
FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::OnRenameSlot, SlotInfo->Name));
const FText Label = LOCTEXT("AnimSlotManagerContextMenuRenameSlotLabel", "Rename Slot");
const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuRenameSlotTooltip", "Rename this slot");
MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action);
}
// Delete Slot
{
FDisplayedSlotNameInfo* SlotInfo = SelectedItemPtr.Get();
FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::OnDeleteSlot, SlotInfo->Name));
const FText Label = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotLabel", "Delete Slot");
const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuDeleteSlotTooltip", "Delete this slot.");
MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action);
}
MenuBuilder.EndSection();
}
MenuBuilder.BeginSection("SlotManagerGeneralActions", LOCTEXT("SlotManagerGeneralActions", "Slot Manager Actions"));
// Add Slot
{
FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::OnAddSlot));
const FText Label = LOCTEXT("AnimSlotManagerContextMenuAddSlotLabel", "Add Slot");
const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuAddSlotTooltip", "Adds a new Slot");
MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action);
}
// Add Group
{
FUIAction Action = FUIAction(FExecuteAction::CreateSP(const_cast<SSkeletonSlotNames*>(this), &SSkeletonSlotNames::OnAddGroup));
const FText Label = LOCTEXT("AnimSlotManagerContextMenuAddGroupLabel", "Add Group");
const FText ToolTipText = LOCTEXT("AnimSlotManagerContextMenuAddGroupTooltip", "Adds a new Group");
MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), Action);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void SSkeletonSlotNames::FillSetSlotGroupSubMenu(FMenuBuilder& MenuBuilder)
{
const TArray<FAnimSlotGroup>& SlotGroups = EditableSkeleton->GetSkeleton().GetSlotGroups();
for (auto SlotGroup : SlotGroups)
{
const FName& GroupName = SlotGroup.GroupName;
const FText ToolTipText = FText::Format(LOCTEXT("ContextMenuSetSlotSubMenuToolTip", "Changes slot's group to {0}"), FText::FromName(GroupName));
/* FString Label = Class->GetDisplayNameText().ToString();*/
const FText Label = FText::FromName(GroupName);
FUIAction UIAction;
UIAction.ExecuteAction.BindRaw(this, &SSkeletonSlotNames::ContextMenuOnSetSlot, GroupName);
MenuBuilder.AddMenuEntry(Label, ToolTipText, FSlateIcon(), UIAction);
}
}
void SSkeletonSlotNames::ContextMenuOnSetSlot(FName InNewGroupName)
{
TArray< TSharedPtr< FDisplayedSlotNameInfo > > SelectedItems = SlotNameListView.Get()->GetSelectedItems();
bool bHasSelectedItem = (SelectedItems.Num() > 0);
bool bShowSlotItem = bHasSelectedItem && !SelectedItems[0].Get()->bIsGroupItem;
if (bShowSlotItem)
{
const FName SlotName = SelectedItems[0].Get()->Name;
if (EditableSkeleton->GetSkeleton().ContainsSlotName(SlotName))
{
EditableSkeleton->SetSlotGroupName(SlotName, InNewGroupName);
RefreshSlotNameListWithFilter();
}
// Highlight newly created item.
TSharedPtr< FDisplayedSlotNameInfo > Item = FindItemNamed(SlotName);
if (Item.IsValid())
{
SlotNameListView->SetSelection(Item);
}
FSlateApplication::Get().DismissAllMenus();
}
}
void SSkeletonSlotNames::OnNotifySelectionChanged(TSharedPtr<FDisplayedSlotNameInfo> Selection, ESelectInfo::Type SelectInfo)
{
if(Selection.IsValid())
{
ShowNotifyInDetailsView(Selection->Name);
}
}
void SSkeletonSlotNames::OnSaveSkeleton()
{
TArray< UPackage* > PackagesToSave;
PackagesToSave.Add(EditableSkeleton->GetSkeleton().GetOutermost());
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, /*bCheckDirty=*/ false, /*bPromptToSave=*/ false);
}
void SSkeletonSlotNames::OnAddSlot()
{
TSharedRef<STextEntryPopup> TextEntry =
SNew(STextEntryPopup)
.Label(LOCTEXT("NewSlotName_AskSlotName", "New Slot Name"))
.OnTextCommitted(this, &SSkeletonSlotNames::AddSlotPopUpOnCommit);
// Show dialog to enter new track name
FSlateApplication::Get().PushMenu(
SharedThis(this),
FWidgetPath(),
TextEntry,
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
);
}
void SSkeletonSlotNames::OnAddGroup()
{
TSharedRef<STextEntryPopup> TextEntry =
SNew(STextEntryPopup)
.Label(LOCTEXT("NewGroupName_AskGroupName", "New Group Name"))
.OnTextCommitted(this, &SSkeletonSlotNames::AddGroupPopUpOnCommit);
// Show dialog to enter new track name
FSlateApplication::Get().PushMenu(
SharedThis(this),
FWidgetPath(),
TextEntry,
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
);
}
void SSkeletonSlotNames::AddSlotPopUpOnCommit(const FText & InNewSlotText, ETextCommit::Type CommitInfo)
{
if (!InNewSlotText.IsEmpty())
{
const FScopedTransaction Transaction(LOCTEXT("NewSlotName_AddSlotName", "Add New Slot Node Name"));
FName NewSlotName = FName(*InNewSlotText.ToString());
const USkeleton& Skeleton = EditableSkeleton->GetSkeleton();
// Keep slot and group names unique
if (!Skeleton.ContainsSlotName(NewSlotName) && (Skeleton.FindAnimSlotGroup(NewSlotName) == nullptr))
{
TArray< TSharedPtr< FDisplayedSlotNameInfo > > SelectedItems = SlotNameListView->GetSelectedItems();
bool bHasSelectedItem = (SelectedItems.Num() > 0);
bool bShowGroupItem = bHasSelectedItem && SelectedItems[0].Get()->bIsGroupItem;
EditableSkeleton->SetSlotGroupName(NewSlotName, bShowGroupItem ? SelectedItems[0].Get()->Name : FAnimSlotGroup::DefaultGroupName);
RefreshSlotNameListWithFilter();
}
// Highlight newly created item.
TSharedPtr< FDisplayedSlotNameInfo > Item = FindItemNamed(NewSlotName);
if (Item.IsValid())
{
SlotNameListView->SetSelection(Item);
}
FSlateApplication::Get().DismissAllMenus();
}
}
void SSkeletonSlotNames::AddGroupPopUpOnCommit(const FText & InNewGroupText, ETextCommit::Type CommitInfo)
{
if (!InNewGroupText.IsEmpty())
{
FName NewGroupName = FName(*InNewGroupText.ToString());
const USkeleton& Skeleton = EditableSkeleton->GetSkeleton();
// Keep slot and group names unique
if (!Skeleton.ContainsSlotName(NewGroupName) && EditableSkeleton->AddSlotGroupName(NewGroupName))
{
RefreshSlotNameListWithFilter();
}
// Highlight newly created item.
TSharedPtr< FDisplayedSlotNameInfo > Item = FindItemNamed(NewGroupName);
if (Item.IsValid())
{
SlotNameListView->SetSelection(Item);
}
FSlateApplication::Get().DismissAllMenus();
}
}
void SSkeletonSlotNames::GetCompatibleAnimBlueprints( TArray<FAssetData>& OutAssets )
{
//Get the skeleton tag to search for
const USkeleton& Skeleton = EditableSkeleton->GetSkeleton();
FString SkeletonExportName = FAssetData(&Skeleton).GetExportTextName();
// Load the asset registry module
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FAssetData> AssetDataList;
AssetRegistryModule.Get().GetAssetsByClass(UAnimBlueprint::StaticClass()->GetClassPathName(), AssetDataList, true);
OutAssets.Empty(AssetDataList.Num());
for(FAssetData& Data : AssetDataList)
{
const FString AssetSkeleton = Data.GetTagValueRef<FString>("TargetSkeleton");
if(AssetSkeleton == SkeletonExportName)
{
OutAssets.Add(Data);
}
}
}
void SSkeletonSlotNames::RefreshSlotNameListWithFilter()
{
CreateSlotNameList( NameFilterBox->GetText().ToString() );
}
void SSkeletonSlotNames::CreateSlotNameList(const FString& SearchText)
{
NotifyList.Empty();
const USkeleton& Skeleton = EditableSkeleton->GetSkeleton();
const TArray<FAnimSlotGroup>& SlotGroups = Skeleton.GetSlotGroups();
for (auto SlotGroup : SlotGroups)
{
const FName& GroupName = SlotGroup.GroupName;
TSharedRef<FDisplayedSlotNameInfo> GroupItem = FDisplayedSlotNameInfo::Make(GroupName, true);
SlotNameListView->SetItemExpansion(GroupItem, true);
NotifyList.Add(GroupItem);
for (auto SlotName : SlotGroup.SlotNames)
{
if (SearchText.IsEmpty() || GroupName.ToString().Contains(SearchText) || SlotName.ToString().Contains(SearchText))
{
TSharedRef<FDisplayedSlotNameInfo> SlotItem = FDisplayedSlotNameInfo::Make(SlotName, false);
SlotNameListView->SetItemExpansion(SlotItem, true);
NotifyList[NotifyList.Num() - 1]->Children.Add(SlotItem);
}
}
}
SlotNameListView->RequestTreeRefresh();
}
TSharedPtr< FDisplayedSlotNameInfo > SSkeletonSlotNames::FindItemNamed(FName ItemName) const
{
for (auto SlotGroupItem : NotifyList)
{
if (SlotGroupItem->Name == ItemName)
{
return SlotGroupItem;
}
for (auto SlotItem : SlotGroupItem->Children)
{
if (SlotItem->Name == ItemName)
{
return SlotItem;
}
}
}
return NULL;
}
void SSkeletonSlotNames::ShowNotifyInDetailsView(FName NotifyName)
{
// @todo nothing to show now, but in the future
// we can show the list of montage that are used by this slot node?
}
void SSkeletonSlotNames::GetCompatibleAnimMontages(TArray<struct FAssetData>& OutAssets)
{
//Get the skeleton tag to search for
const USkeleton& Skeleton = EditableSkeleton->GetSkeleton();
FString SkeletonExportName = FAssetData(&Skeleton).GetExportTextName();
// Load the asset registry module
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FAssetData> AssetDataList;
AssetRegistryModule.Get().GetAssetsByClass(UAnimMontage::StaticClass()->GetClassPathName(), AssetDataList, true);
OutAssets.Empty(AssetDataList.Num());
for( int32 AssetIndex = 0; AssetIndex < AssetDataList.Num(); ++AssetIndex )
{
const FAssetData& PossibleAnimMontage = AssetDataList[AssetIndex];
if( SkeletonExportName == PossibleAnimMontage.GetTagValueRef<FString>("Skeleton") )
{
OutAssets.Add( PossibleAnimMontage );
}
}
}
UObject* SSkeletonSlotNames::ShowInDetailsView( UClass* EdClass )
{
UObject *Obj = EditorObjectTracker.GetEditorObjectForClass(EdClass);
if(Obj != NULL)
{
OnObjectSelected.ExecuteIfBound(Obj);
}
return Obj;
}
void SSkeletonSlotNames::ClearDetailsView()
{
OnObjectSelected.ExecuteIfBound(nullptr);
}
void SSkeletonSlotNames::PostUndoRedo()
{
RefreshSlotNameListWithFilter();
}
void SSkeletonSlotNames::AddReferencedObjects( FReferenceCollector& Collector )
{
EditorObjectTracker.AddReferencedObjects(Collector);
}
void SSkeletonSlotNames::NotifyUser( FNotificationInfo& NotificationInfo )
{
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification( NotificationInfo );
if ( Notification.IsValid() )
{
Notification->SetCompletionState( SNotificationItem::CS_Fail );
}
}
void SSkeletonSlotNames::OnDeleteSlot(FName SlotName)
{
TArray<FAssetData> CompatibleMontages;
TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> CompatibleSlotNodes;
GetMontagesAndNodesUsingSlot(SlotName, CompatibleMontages, CompatibleSlotNodes);
if(CompatibleMontages.Num() > 0 || CompatibleSlotNodes.Num() > 0)
{
// We can't delete here - still have references. Give the user a chance to fix.
if(!ReferenceWindow.IsValid())
{
// No existing window
SAssignNew(ReferenceWindow, SWindow)
.AutoCenter(EAutoCenter::PreferredWorkArea)
.SizingRule(ESizingRule::Autosized)
.Title(LOCTEXT("ReferenceWindowTitle", "Slot References"));
ReferenceWindow->SetContent
(
SNew(SBorder)
.Padding(FMargin(3))
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SAssignNew(ReferenceWidget, SSlotNameReferenceWindow)
.ReferencingMontages(&CompatibleMontages)
.ReferencingNodes(&CompatibleSlotNodes)
.SlotName(SlotName.ToString())
.OperationText(LOCTEXT("DeleteOperation", "Delete"))
.WidgetWindow(ReferenceWindow)
.OnRetry(FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryDeleteSlot, SlotName))
]
);
TSharedPtr<SWindow> ParentWindow;
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
ParentWindow = MainFrameModule.GetParentWindow();
FSlateApplication::Get().AddWindowAsNativeChild(ReferenceWindow.ToSharedRef(), ParentWindow.ToSharedRef());
ReferenceWindow->SetOnWindowClosed(FOnWindowClosed::CreateSP(this, &SSkeletonSlotNames::ReferenceWindowClosed));
}
else
{
TSharedPtr<SSlotNameReferenceWindow> RefWidgetPinned = ReferenceWidget.Pin();
if(RefWidgetPinned.IsValid())
{
FReferenceWindowInfo WindowInfo;
WindowInfo.ReferencingMontages = &CompatibleMontages;
WindowInfo.ReferencingNodes = &CompatibleSlotNodes;
WindowInfo.ItemText = FText::FromName(SlotName);
WindowInfo.OperationText = LOCTEXT("DeleteOperation", "Delete");
WindowInfo.RetryDelegate = FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryDeleteSlot, SlotName);
RefWidgetPinned->UpdateInfo(WindowInfo);
ReferenceWindow->BringToFront();
}
}
}
else
{
DeleteSlot(SlotName);
}
}
void SSkeletonSlotNames::DeleteSlot(const FName& SlotName)
{
const USkeleton& Skeleton = EditableSkeleton->GetSkeleton();
if(Skeleton.ContainsSlotName(SlotName))
{
EditableSkeleton->DeleteSlotName(SlotName);
RefreshSlotNameListWithFilter();
}
}
void SSkeletonSlotNames::GetAnimMontagesUsingSlot(FName SlotName, TArray<FAssetData>& OutMontages)
{
TArray<FAssetData> SkeletonCompatibleMontages;
GetCompatibleAnimMontages(SkeletonCompatibleMontages);
for(FAssetData& MontageData : SkeletonCompatibleMontages)
{
UAnimMontage* Montage = Cast<UAnimMontage>(MontageData.GetAsset());
check(Montage);
for(FSlotAnimationTrack& SlotTrack : Montage->SlotAnimTracks)
{
if(SlotTrack.SlotName == SlotName)
{
OutMontages.Add(MontageData);
}
}
}
}
void SSkeletonSlotNames::GetAnimMontagesUsingSlotGroup(FName SlotGroupName, TArray<FAssetData>& OutMontages)
{
if(const FAnimSlotGroup* Group = EditableSkeleton->GetSkeleton().FindAnimSlotGroup(SlotGroupName))
{
for(const FName& SlotName : Group->SlotNames)
{
GetAnimMontagesUsingSlot(SlotName, OutMontages);
}
}
}
void SSkeletonSlotNames::ReferenceWindowClosed(const TSharedRef<SWindow>& Window)
{
ReferenceWindow = nullptr;
}
void SSkeletonSlotNames::RetryDeleteSlot(FName SlotName)
{
TArray<FAssetData> CompatibleMontages;
TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> CompatibleSlotNodes;
GetMontagesAndNodesUsingSlot(SlotName, CompatibleMontages, CompatibleSlotNodes);
if(CompatibleMontages.Num() > 0 || CompatibleSlotNodes.Num() > 0)
{
// Still can't delete
TSharedPtr<SSlotNameReferenceWindow> PinnedWidget = ReferenceWidget.Pin();
if(PinnedWidget.IsValid())
{
FReferenceWindowInfo WindowInfo;
WindowInfo.ReferencingMontages = &CompatibleMontages;
WindowInfo.ReferencingNodes = &CompatibleSlotNodes;
WindowInfo.ItemText = FText::FromName(SlotName);
WindowInfo.OperationText = LOCTEXT("DeleteOperation", "Delete");
WindowInfo.RetryDelegate = FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryDeleteSlot, SlotName);
PinnedWidget->UpdateInfo(WindowInfo);
ReferenceWindow->BringToFront();
}
}
else
{
ReferenceWindow->RequestDestroyWindow();
DeleteSlot(SlotName);
}
}
void SSkeletonSlotNames::GetMontagesAndNodesUsingSlot(const FName& SlotName, TArray<FAssetData>& CompatibleMontages, TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> &CompatibleSlotNodes)
{
FScopedSlowTask SlowTask(3, LOCTEXT("AssetReferenceSlowTaskMessage", "Checking for slot references."));
SlowTask.MakeDialog();
TArray<FAssetData> CompatibleBlueprints;
SlowTask.EnterProgressFrame(1, LOCTEXT("AssetReferenceTask_Blueprints", "Searching Blueprints"));
GetCompatibleAnimBlueprints(CompatibleBlueprints);
SlowTask.EnterProgressFrame(1, LOCTEXT("AssetReferenceTask_Montages", "Searching Montages"));
GetAnimMontagesUsingSlot(SlotName, CompatibleMontages);
SlowTask.EnterProgressFrame(1, LOCTEXT("AssetReferenceTask_Nodes", "Searching Nodes"));
for(FAssetData& Data : CompatibleBlueprints)
{
TArray<UEdGraph*> BPGraphs;
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(Data.GetAsset());
AnimBP->GetAllGraphs(BPGraphs);
for(UEdGraph* Graph : BPGraphs)
{
TArray<UAnimGraphNode_Slot*> SlotNodes;
Graph->GetNodesOfClass(SlotNodes);
for(UAnimGraphNode_Slot* SlotNode : SlotNodes)
{
if(SlotNode->Node.SlotName == SlotName)
{
CompatibleSlotNodes.Add(AnimBP, SlotNode);
}
}
}
}
// If we end up loading in any previously unsaved assets they can add names to the list so refresh
RefreshSlotNameListWithFilter();
}
void SSkeletonSlotNames::GetMontagesAndNodesUsingSlotGroup(const FName& SlotGroupName, TArray<FAssetData>& OutMontages, TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> &OutBlueprintSlotMap)
{
if(const FAnimSlotGroup* SlotGroup = EditableSkeleton->GetSkeleton().FindAnimSlotGroup(SlotGroupName))
{
for(const auto& SlotName : SlotGroup->SlotNames)
{
GetMontagesAndNodesUsingSlot(SlotName, OutMontages, OutBlueprintSlotMap);
}
}
}
void SSkeletonSlotNames::OnRenameSlotPopupCommitted(const FText & InNewSlotText, ETextCommit::Type CommitInfo, FName OldName)
{
if(CommitInfo == ETextCommit::OnEnter)
{
FName NewName(*InNewSlotText.ToString());
// Need to dismiss menus early or the slow task in GetMontagesAndNodesUsingSlot will fail to show onscreen
FSlateApplication::Get().DismissAllMenus();
// Make sure the name doesn't already exist
if(EditableSkeleton->GetSkeleton().ContainsSlotName(NewName))
{
FNotificationInfo Notification(FText::Format(LOCTEXT("ToastRenameFailDesc", "Rename Failed! Slot name {0} already exists in the target skeleton."), FText::FromName(NewName)));
Notification.ExpireDuration = 3.0f;
Notification.bFireAndForget = true;
NotifyUser(Notification);
return;
}
// Validate references
TArray<FAssetData> CompatibleMontages;
TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> CompatibleSlotNodes;
GetMontagesAndNodesUsingSlot(OldName, CompatibleMontages, CompatibleSlotNodes);
if(CompatibleMontages.Num() > 0 || CompatibleSlotNodes.Num() > 0)
{
// We can't rename here - still have references. Give the user a chance to fix.
if(!ReferenceWindow.IsValid())
{
// No existing window
SAssignNew(ReferenceWindow, SWindow)
.AutoCenter(EAutoCenter::PreferredWorkArea)
.SizingRule(ESizingRule::Autosized)
.Title(LOCTEXT("ReferenceWindowTitle", "Slot References"));
ReferenceWindow->SetContent
(
SNew(SBorder)
.Padding(FMargin(3))
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SAssignNew(ReferenceWidget, SSlotNameReferenceWindow)
.ReferencingMontages(&CompatibleMontages)
.ReferencingNodes(&CompatibleSlotNodes)
.SlotName(OldName.ToString())
.OperationText(LOCTEXT("RenameOperation", "Rename"))
.WidgetWindow(ReferenceWindow)
.OnRetry(FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryRenameSlot, OldName, NewName))
]
);
TSharedPtr<SWindow> ParentWindow;
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
ParentWindow = MainFrameModule.GetParentWindow();
FSlateApplication::Get().AddWindowAsNativeChild(ReferenceWindow.ToSharedRef(), ParentWindow.ToSharedRef());
ReferenceWindow->SetOnWindowClosed(FOnWindowClosed::CreateSP(this, &SSkeletonSlotNames::ReferenceWindowClosed));
}
else
{
TSharedPtr<SSlotNameReferenceWindow> RefWidgetPinned = ReferenceWidget.Pin();
if(RefWidgetPinned.IsValid())
{
FReferenceWindowInfo WindowInfo;
WindowInfo.ReferencingMontages = &CompatibleMontages;
WindowInfo.ReferencingNodes = &CompatibleSlotNodes;
WindowInfo.ItemText = FText::FromName(OldName);
WindowInfo.OperationText = LOCTEXT("RenameOperation", "Rename");
WindowInfo.RetryDelegate = FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryRenameSlot, OldName, NewName);
RefWidgetPinned->UpdateInfo(WindowInfo);
ReferenceWindow->BringToFront();
}
}
}
else
{
RenameSlot(OldName, NewName);
}
}
}
void SSkeletonSlotNames::OnRenameSlot(FName CurrentName)
{
TSharedRef<STextEntryPopup> TextEntry =
SNew(STextEntryPopup)
.Label(LOCTEXT("RenameSlotName_AskSlotName", "New Slot Name"))
.OnTextCommitted(this, &SSkeletonSlotNames::OnRenameSlotPopupCommitted, CurrentName);
// Show dialog to enter new track name
FSlateApplication::Get().PushMenu(
SharedThis(this),
FWidgetPath(),
TextEntry,
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
);
}
void SSkeletonSlotNames::RenameSlot(FName CurrentName, FName NewName)
{
if(EditableSkeleton->GetSkeleton().ContainsSlotName(CurrentName))
{
EditableSkeleton->RenameSlotName(CurrentName, NewName);
RefreshSlotNameListWithFilter();
}
}
void SSkeletonSlotNames::RetryRenameSlot(FName CurrentName, FName NewName)
{
TArray<FAssetData> CompatibleMontages;
TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> CompatibleSlotNodes;
GetMontagesAndNodesUsingSlot(CurrentName, CompatibleMontages, CompatibleSlotNodes);
if(CompatibleMontages.Num() > 0 || CompatibleSlotNodes.Num() > 0)
{
// Still can't rename
TSharedPtr<SSlotNameReferenceWindow> PinnedWidget = ReferenceWidget.Pin();
if(PinnedWidget.IsValid())
{
FReferenceWindowInfo WindowInfo;
WindowInfo.ReferencingMontages = &CompatibleMontages;
WindowInfo.ReferencingNodes = &CompatibleSlotNodes;
WindowInfo.ItemText = FText::FromName(CurrentName);
WindowInfo.OperationText = LOCTEXT("DeleteOperation", "Delete");
WindowInfo.RetryDelegate = FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryRenameSlot, CurrentName, NewName);
PinnedWidget->UpdateInfo(WindowInfo);
ReferenceWindow->BringToFront();
}
}
else
{
ReferenceWindow->RequestDestroyWindow();
RenameSlot(CurrentName, NewName);
}
}
void SSkeletonSlotNames::OnDeleteSlotGroup(FName GroupName)
{
TArray<FAssetData> CompatibleMontages;
TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> CompatibleSlotNodes;
GetMontagesAndNodesUsingSlotGroup(GroupName, CompatibleMontages, CompatibleSlotNodes);
if(CompatibleMontages.Num() > 0 || CompatibleSlotNodes.Num() > 0)
{
// Can't delete, still referenced
// We can't rename here - still have references. Give the user a chance to fix.
if(!ReferenceWindow.IsValid())
{
// No existing window
SAssignNew(ReferenceWindow, SWindow)
.AutoCenter(EAutoCenter::PreferredWorkArea)
.SizingRule(ESizingRule::Autosized)
.Title(LOCTEXT("ReferenceWindowTitle", "Slot References"));
ReferenceWindow->SetContent
(
SNew(SBorder)
.Padding(FMargin(3))
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SAssignNew(ReferenceWidget, SSlotNameReferenceWindow)
.ReferencingMontages(&CompatibleMontages)
.ReferencingNodes(&CompatibleSlotNodes)
.SlotName(GroupName.ToString())
.OperationText(LOCTEXT("DeleteGroupOperation", "Delete Group"))
.WidgetWindow(ReferenceWindow)
.OnRetry(FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryDeleteSlotGroup, GroupName))
]
);
TSharedPtr<SWindow> ParentWindow;
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
ParentWindow = MainFrameModule.GetParentWindow();
FSlateApplication::Get().AddWindowAsNativeChild(ReferenceWindow.ToSharedRef(), ParentWindow.ToSharedRef());
ReferenceWindow->SetOnWindowClosed(FOnWindowClosed::CreateSP(this, &SSkeletonSlotNames::ReferenceWindowClosed));
}
else
{
TSharedPtr<SSlotNameReferenceWindow> RefWidgetPinned = ReferenceWidget.Pin();
if(RefWidgetPinned.IsValid())
{
FReferenceWindowInfo WindowInfo;
WindowInfo.ReferencingMontages = &CompatibleMontages;
WindowInfo.ReferencingNodes = &CompatibleSlotNodes;
WindowInfo.ItemText = FText::FromName(GroupName);
WindowInfo.OperationText = LOCTEXT("DeleteGroupOperation", "Delete Group");
WindowInfo.RetryDelegate = FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryDeleteSlotGroup, GroupName);
RefWidgetPinned->UpdateInfo(WindowInfo);
ReferenceWindow->BringToFront();
}
}
}
else
{
DeleteSlotGroup(GroupName);
}
}
void SSkeletonSlotNames::RetryDeleteSlotGroup(FName GroupName)
{
TArray<FAssetData> CompatibleMontages;
TMultiMap<UAnimBlueprint*, UAnimGraphNode_Slot*> CompatibleSlotNodes;
GetMontagesAndNodesUsingSlotGroup(GroupName, CompatibleMontages, CompatibleSlotNodes);
if(CompatibleMontages.Num() > 0 || CompatibleSlotNodes.Num() > 0)
{
// Still can't rename
TSharedPtr<SSlotNameReferenceWindow> PinnedWidget = ReferenceWidget.Pin();
if(PinnedWidget.IsValid())
{
FReferenceWindowInfo WindowInfo;
WindowInfo.ReferencingMontages = &CompatibleMontages;
WindowInfo.ReferencingNodes = &CompatibleSlotNodes;
WindowInfo.ItemText = FText::FromName(GroupName);
WindowInfo.OperationText = LOCTEXT("DeleteGroupOperation", "Delete Group");
WindowInfo.RetryDelegate = FSimpleDelegate::CreateSP(this, &SSkeletonSlotNames::RetryDeleteSlotGroup, GroupName);
PinnedWidget->UpdateInfo(WindowInfo);
ReferenceWindow->BringToFront();
}
}
else
{
ReferenceWindow->RequestDestroyWindow();
DeleteSlotGroup(GroupName);
}
}
bool SSkeletonSlotNames::CanDeleteSlotGroup(FName GroupName)
{
static const FName DefaultGroupName("DefaultGroup");
return GroupName != DefaultGroupName;
}
void SSkeletonSlotNames::DeleteSlotGroup(const FName& GroupName)
{
if(EditableSkeleton->GetSkeleton().FindAnimSlotGroup(GroupName) != nullptr)
{
EditableSkeleton->DeleteSlotGroup(GroupName);
RefreshSlotNameListWithFilter();
}
}
#undef LOCTEXT_NAMESPACE