// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #include "SSkeletonAnimNotifies.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "AssetData.h" #include "Animation/AnimSequenceBase.h" #include "EditorStyleSet.h" #include "Animation/EditorSkeletonNotifyObj.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "ScopedTransaction.h" #include "IEditableSkeleton.h" #include "TabSpawners.h" #define LOCTEXT_NAMESPACE "SkeletonAnimNotifies" static const FName ColumnId_AnimNotifyNameLabel( "AnimNotifyName" ); ////////////////////////////////////////////////////////////////////////// // SMorphTargetListRow typedef TSharedPtr< FDisplayedAnimNotifyInfo > FDisplayedAnimNotifyInfoPtr; class SAnimNotifyListRow : public SMultiColumnTableRow< FDisplayedAnimNotifyInfoPtr > { public: SLATE_BEGIN_ARGS( SAnimNotifyListRow ) {} /** The item for this row **/ SLATE_ARGUMENT( FDisplayedAnimNotifyInfoPtr, Item ) /* Widget used to display the list of morph targets */ SLATE_ARGUMENT( TSharedPtr, NotifiesListView ) SLATE_END_ARGS() void Construct( const FArguments& InArgs, const TSharedRef& OwnerTableView ); /** Overridden from SMultiColumnTableRow. Generates a widget for this column of the tree row. */ virtual TSharedRef GenerateWidgetForColumn( const FName& ColumnName ) override; private: /** Widget used to display the list of notifies */ TSharedPtr NotifiesListView; /** The notify being displayed by this row */ FDisplayedAnimNotifyInfoPtr Item; }; void SAnimNotifyListRow::Construct( const FArguments& InArgs, const TSharedRef& InOwnerTableView ) { Item = InArgs._Item; NotifiesListView = InArgs._NotifiesListView; check( Item.IsValid() ); SMultiColumnTableRow< FDisplayedAnimNotifyInfoPtr >::Construct( FSuperRowType::FArguments(), InOwnerTableView ); } TSharedRef< SWidget > SAnimNotifyListRow::GenerateWidgetForColumn( const FName& ColumnName ) { check( ColumnName == ColumnId_AnimNotifyNameLabel ); return SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() .Padding( 0.0f, 4.0f ) .VAlign( VAlign_Center ) [ SAssignNew(Item->InlineEditableText, SInlineEditableTextBlock) .Text( FText::FromName(Item->Name) ) .OnVerifyTextChanged(NotifiesListView.Get(), &SSkeletonAnimNotifies::OnVerifyNotifyNameCommit, Item) .OnTextCommitted(NotifiesListView.Get(), &SSkeletonAnimNotifies::OnNotifyNameCommitted, Item) .IsSelected(NotifiesListView.Get(), &SSkeletonAnimNotifies::IsSelected) ]; } ///////////////////////////////////////////////////// // FSkeletonAnimNotifiesSummoner FSkeletonAnimNotifiesSummoner::FSkeletonAnimNotifiesSummoner(TSharedPtr InHostingApp, const TSharedRef& InEditableSkeleton, FSimpleMulticastDelegate& InOnChangeAnimNotifies, FSimpleMulticastDelegate& InOnPostUndo, FOnObjectsSelected InOnObjectsSelected) : FWorkflowTabFactory(FPersonaTabs::SkeletonAnimNotifiesID, InHostingApp) , EditableSkeleton(InEditableSkeleton) , OnChangeAnimNotifies(InOnChangeAnimNotifies) , OnPostUndo(InOnPostUndo) , OnObjectsSelected(InOnObjectsSelected) { TabLabel = LOCTEXT("SkeletonAnimNotifiesTabTitle", "Animation Notifies"); TabIcon = FSlateIcon(FEditorStyle::GetStyleSetName(), "Persona.Tabs.AnimationNotifies"); EnableTabPadding(); bIsSingleton = true; ViewMenuDescription = LOCTEXT("SkeletonAnimNotifiesMenu", "Animation Notifies"); ViewMenuTooltip = LOCTEXT("SkeletonAnimNotifies_ToolTip", "Shows the skeletons notifies list"); } TSharedRef FSkeletonAnimNotifiesSummoner::CreateTabBody(const FWorkflowTabSpawnInfo& Info) const { return SNew(SSkeletonAnimNotifies, EditableSkeleton.Pin().ToSharedRef(), OnChangeAnimNotifies, OnPostUndo) .OnObjectsSelected(OnObjectsSelected); } ///////////////////////////////////////////////////// // SSkeletonAnimNotifies void SSkeletonAnimNotifies::Construct(const FArguments& InArgs, const TSharedRef& InEditableSkeleton, FSimpleMulticastDelegate& InOnChangeAnimNotifies, FSimpleMulticastDelegate& InOnPostUndo) { OnObjectsSelected = InArgs._OnObjectsSelected; EditableSkeleton = InEditableSkeleton; InOnChangeAnimNotifies.Add(FSimpleDelegate::CreateSP( this, &SSkeletonAnimNotifies::PostUndo ) ); InOnPostUndo.Add(FSimpleDelegate::CreateSP( this, &SSkeletonAnimNotifies::RefreshNotifiesListWithFilter ) ); this->ChildSlot [ SNew( SVerticalBox ) + SVerticalBox::Slot() .AutoHeight() .Padding( FMargin( 0.0f, 0.0f, 0.0f, 4.0f ) ) [ SAssignNew( NameFilterBox, SSearchBox ) .SelectAllTextWhenFocused( true ) .OnTextChanged( this, &SSkeletonAnimNotifies::OnFilterTextChanged ) .OnTextCommitted( this, &SSkeletonAnimNotifies::OnFilterTextCommitted ) .HintText( LOCTEXT( "NotifiesSearchBoxHint", "Search Animation Notifies...") ) ] + SVerticalBox::Slot() .FillHeight( 1.0f ) // This is required to make the scrollbar work, as content overflows Slate containers by default [ SAssignNew( NotifiesListView, SAnimNotifyListType ) .ListItemsSource( &NotifyList ) .OnGenerateRow( this, &SSkeletonAnimNotifies::GenerateNotifyRow ) .OnContextMenuOpening( this, &SSkeletonAnimNotifies::OnGetContextMenuContent ) .OnSelectionChanged( this, &SSkeletonAnimNotifies::OnNotifySelectionChanged ) .ItemHeight( 22.0f ) .HeaderRow ( SNew( SHeaderRow ) + SHeaderRow::Column( ColumnId_AnimNotifyNameLabel ) .DefaultLabel( LOCTEXT( "AnimNotifyNameLabel", "Notify Name" ) ) ) ] ]; CreateNotifiesList(); } void SSkeletonAnimNotifies::OnFilterTextChanged( const FText& SearchText ) { FilterText = SearchText; RefreshNotifiesListWithFilter(); } void SSkeletonAnimNotifies::OnFilterTextCommitted( const FText& SearchText, ETextCommit::Type CommitInfo ) { // Just do the same as if the user typed in the box OnFilterTextChanged( SearchText ); } TSharedRef SSkeletonAnimNotifies::GenerateNotifyRow(TSharedPtr InInfo, const TSharedRef& OwnerTable) { check( InInfo.IsValid() ); return SNew( SAnimNotifyListRow, OwnerTable ) .Item( InInfo ) .NotifiesListView( SharedThis( this ) ); } TSharedPtr SSkeletonAnimNotifies::OnGetContextMenuContent() const { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, NULL); MenuBuilder.BeginSection("AnimNotifyAction", LOCTEXT( "AnimNotifyActions", "Selected Notify Actions" ) ); { { FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SSkeletonAnimNotifies::OnDeleteAnimNotify ), FCanExecuteAction::CreateSP( this, &SSkeletonAnimNotifies::CanPerformDelete ) ); const FText Label = LOCTEXT("DeleteAnimNotifyButtonLabel", "Delete"); const FText ToolTipText = LOCTEXT("DeleteAnimNotifyButtonTooltip", "Deletes the selected anim notifies."); MenuBuilder.AddMenuEntry( Label, ToolTipText, FSlateIcon(), Action); } { FUIAction Action = FUIAction( FExecuteAction::CreateSP( this, &SSkeletonAnimNotifies::OnRenameAnimNotify ), FCanExecuteAction::CreateSP( this, &SSkeletonAnimNotifies::CanPerformRename ) ); const FText Label = LOCTEXT("RenameAnimNotifyButtonLabel", "Rename"); const FText ToolTipText = LOCTEXT("RenameAnimNotifyButtonTooltip", "Renames the selected anim notifies."); MenuBuilder.AddMenuEntry( Label, ToolTipText, FSlateIcon(), Action); } } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } void SSkeletonAnimNotifies::OnNotifySelectionChanged(TSharedPtr Selection, ESelectInfo::Type SelectInfo) { if(Selection.IsValid()) { ShowNotifyInDetailsView(Selection->Name); } } bool SSkeletonAnimNotifies::CanPerformDelete() const { TArray< TSharedPtr< FDisplayedAnimNotifyInfo > > SelectedRows = NotifiesListView->GetSelectedItems(); return SelectedRows.Num() > 0; } bool SSkeletonAnimNotifies::CanPerformRename() const { TArray< TSharedPtr< FDisplayedAnimNotifyInfo > > SelectedRows = NotifiesListView->GetSelectedItems(); return SelectedRows.Num() == 1; } void SSkeletonAnimNotifies::OnDeleteAnimNotify() { TArray< TSharedPtr< FDisplayedAnimNotifyInfo > > SelectedRows = NotifiesListView->GetSelectedItems(); // this one deletes all notifies with same name. TArray SelectedNotifyNames; for(int Selection = 0; Selection < SelectedRows.Num(); ++Selection) { SelectedNotifyNames.Add(SelectedRows[Selection]->Name); } int32 NumAnimationsModified = EditableSkeleton->DeleteAnimNotifies(SelectedNotifyNames); if(NumAnimationsModified > 0) { // Tell the user that the socket is a duplicate FFormatNamedArguments Args; Args.Add( TEXT("NumAnimationsModified"), NumAnimationsModified ); FNotificationInfo Info( FText::Format( LOCTEXT( "AnimNotifiesDeleted", "{NumAnimationsModified} animation(s) modified to delete notifications" ), Args ) ); Info.bUseLargeFont = false; Info.ExpireDuration = 5.0f; NotifyUser( Info ); } CreateNotifiesList( NameFilterBox->GetText().ToString() ); } void SSkeletonAnimNotifies::OnRenameAnimNotify() { TArray< TSharedPtr< FDisplayedAnimNotifyInfo > > SelectedRows = NotifiesListView->GetSelectedItems(); check(SelectedRows.Num() == 1); // Should be guaranteed by CanPerformRename SelectedRows[0]->InlineEditableText->EnterEditingMode(); } bool SSkeletonAnimNotifies::OnVerifyNotifyNameCommit( const FText& NewName, FText& OutErrorMessage, TSharedPtr Item ) { bool bValid(true); if(NewName.IsEmpty()) { OutErrorMessage = LOCTEXT( "NameMissing_Error", "You must provide a name." ); bValid = false; } FName NotifyName( *NewName.ToString() ); if(NotifyName != Item->Name) { if(EditableSkeleton->GetSkeleton().AnimationNotifies.Contains(NotifyName)) { OutErrorMessage = FText::Format( LOCTEXT("AlreadyInUseMessage", "'{0}' is already in use."), NewName ); bValid = false; } } return bValid; } void SSkeletonAnimNotifies::OnNotifyNameCommitted( const FText& NewName, ETextCommit::Type, TSharedPtr Item ) { int32 NumAnimationsModified = EditableSkeleton->RenameNotify(FName(*NewName.ToString()), Item->Name); if(NumAnimationsModified > 0) { // Tell the user that the socket is a duplicate FFormatNamedArguments Args; Args.Add( TEXT("NumAnimationsModified"), NumAnimationsModified ); FNotificationInfo Info( FText::Format( LOCTEXT( "AnimNotifiesRenamed", "{NumAnimationsModified} animation(s) modified to rename notification" ), Args ) ); Info.bUseLargeFont = false; Info.ExpireDuration = 5.0f; NotifyUser( Info ); } RefreshNotifiesListWithFilter(); } void SSkeletonAnimNotifies::RefreshNotifiesListWithFilter() { CreateNotifiesList( NameFilterBox->GetText().ToString() ); } void SSkeletonAnimNotifies::CreateNotifiesList( const FString& SearchText ) { NotifyList.Empty(); for(int i = 0; i < EditableSkeleton->GetSkeleton().AnimationNotifies.Num(); ++i) { const FName& NotifyName = EditableSkeleton->GetSkeleton().AnimationNotifies[i]; if ( !SearchText.IsEmpty() ) { if ( NotifyName.ToString().Contains( SearchText ) ) { NotifyList.Add( FDisplayedAnimNotifyInfo::Make( NotifyName ) ); } } else { NotifyList.Add( FDisplayedAnimNotifyInfo::Make( NotifyName ) ); } } NotifiesListView->RequestListRefresh(); } void SSkeletonAnimNotifies::ShowNotifyInDetailsView(FName NotifyName) { UEditorSkeletonNotifyObj *Obj = Cast(ShowInDetailsView(UEditorSkeletonNotifyObj::StaticClass())); if(Obj != NULL) { Obj->AnimationNames.Empty(); TArray CompatibleAnimSequences; EditableSkeleton->GetCompatibleAnimSequences(CompatibleAnimSequences); for( int32 AssetIndex = 0; AssetIndex < CompatibleAnimSequences.Num(); ++AssetIndex ) { const FAssetData& PossibleAnimSequence = CompatibleAnimSequences[AssetIndex]; UAnimSequenceBase* Sequence = Cast(PossibleAnimSequence.GetAsset()); for(int32 NotifyIndex = 0; NotifyIndex < Sequence->Notifies.Num(); ++NotifyIndex) { FAnimNotifyEvent& NotifyEvent = Sequence->Notifies[NotifyIndex]; if(NotifyEvent.NotifyName == NotifyName) { Obj->AnimationNames.Add(MakeShareable(new FString(PossibleAnimSequence.AssetName.ToString()))); break; } } } Obj->Name = NotifyName; } } UObject* SSkeletonAnimNotifies::ShowInDetailsView( UClass* EdClass ) { UObject *Obj = EditorObjectTracker.GetEditorObjectForClass(EdClass); if(Obj != NULL) { TArray Objects; Objects.Add(Obj); OnObjectsSelected.ExecuteIfBound(Objects); } return Obj; } void SSkeletonAnimNotifies::ClearDetailsView() { TArray Objects; OnObjectsSelected.ExecuteIfBound(Objects); } void SSkeletonAnimNotifies::PostUndo() { RefreshNotifiesListWithFilter(); } void SSkeletonAnimNotifies::AddReferencedObjects( FReferenceCollector& Collector ) { EditorObjectTracker.AddReferencedObjects(Collector); } void SSkeletonAnimNotifies::NotifyUser( FNotificationInfo& NotificationInfo ) { TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification( NotificationInfo ); if ( Notification.IsValid() ) { Notification->SetCompletionState( SNotificationItem::CS_Fail ); } } #undef LOCTEXT_NAMESPACE