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

349 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimTimeline/AnimTimelineTrack_NotifiesPanel.h"
#include "SAnimNotifyPanel.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Widgets/Layout/SBox.h"
#include "PersonaUtils.h"
#include "AnimSequenceTimelineCommands.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "AnimTimeline/AnimTimelineTrack_Notifies.h"
#include "ScopedTransaction.h"
#include "Widgets/Views/SExpanderArrow.h"
#include "Widgets/Layout/SBorder.h"
#include "AnimTimeline/SAnimOutlinerItem.h"
#include "Animation/AnimMontage.h"
#include "AnimTimeline/AnimModel_AnimSequenceBase.h"
#define LOCTEXT_NAMESPACE "FAnimTimelineTrack_NotifiesPanel"
const float FAnimTimelineTrack_NotifiesPanel::NotificationTrackHeight = 24.0f;
const FName FAnimTimelineTrack_NotifiesPanel::AnimationEditorStatusBarName = FName(TEXT("AssetEditor.AnimationEditor.MainMenu"));
ANIMTIMELINE_IMPLEMENT_TRACK(FAnimTimelineTrack_NotifiesPanel);
FAnimTimelineTrack_NotifiesPanel::FAnimTimelineTrack_NotifiesPanel(const TSharedRef<FAnimModel>& InModel)
: FAnimTimelineTrack(FText::GetEmpty(), FText::GetEmpty(), InModel)
, PendingRenameTrackIndex(INDEX_NONE)
{
SetHeight((float)InModel->GetAnimSequenceBase()->AnimNotifyTracks.Num() * NotificationTrackHeight);
}
TSharedRef<SWidget> FAnimTimelineTrack_NotifiesPanel::GenerateContainerWidgetForTimeline()
{
GetAnimNotifyPanel();
AnimNotifyPanel->Update();
return AnimNotifyPanel.ToSharedRef();
}
TSharedRef<SWidget> FAnimTimelineTrack_NotifiesPanel::GenerateContainerWidgetForOutliner(const TSharedRef<SAnimOutlinerItem>& InRow)
{
TSharedRef<SWidget> Widget =
SNew(SHorizontalBox)
.ToolTipText(this, &FAnimTimelineTrack::GetToolTipText)
+SHorizontalBox::Slot()
[
SAssignNew(OutlinerWidget, SVerticalBox)
];
RefreshOutlinerWidget();
return Widget;
}
void FAnimTimelineTrack_NotifiesPanel::RefreshOutlinerWidget()
{
OutlinerWidget->ClearChildren();
int32 TrackIndex = 0;
UAnimSequenceBase* AnimSequence = GetModel()->GetAnimSequenceBase();
for(FAnimNotifyTrack& AnimNotifyTrack : AnimSequence->AnimNotifyTracks)
{
TSharedPtr<SBox> SlotBox;
TSharedPtr<SInlineEditableTextBlock> InlineEditableTextBlock;
OutlinerWidget->AddSlot()
.AutoHeight()
[
SAssignNew(SlotBox, SBox)
.HeightOverride(NotificationTrackHeight)
];
TSharedPtr<SHorizontalBox> HorizontalBox;
SlotBox->SetContent(
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Sequencer.Section.BackgroundTint"))
.BorderBackgroundColor(FAppStyle::GetColor("AnimTimeline.Outliner.ItemColor"))
[
SAssignNew(HorizontalBox, SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
.Padding(30.0f, 0.0f, 0.0f, 0.0f)
[
SAssignNew(InlineEditableTextBlock, SInlineEditableTextBlock)
.Text_Lambda([TrackIndex, AnimSequence](){ return AnimSequence->AnimNotifyTracks.IsValidIndex(TrackIndex) ? FText::FromName(AnimSequence->AnimNotifyTracks[TrackIndex].TrackName) : FText::GetEmpty(); })
.IsSelected(FIsSelected::CreateLambda([](){ return true; }))
.OnTextCommitted(this, &FAnimTimelineTrack_NotifiesPanel::OnCommitTrackName, TrackIndex)
]
]
);
UAnimMontage* AnimMontage = Cast<UAnimMontage>(GetModel()->GetAnimSequenceBase());
if(!(AnimMontage && AnimMontage->HasParentAsset()))
{
HorizontalBox->AddSlot()
.AutoWidth()
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
.Padding(OutlinerRightPadding, 1.0f)
[
PersonaUtils::MakeTrackButton(LOCTEXT("AddTrackButtonText", "Track"), FOnGetContent::CreateSP(this, &FAnimTimelineTrack_NotifiesPanel::BuildNotifiesPanelSubMenu, TrackIndex), MakeAttributeSP(SlotBox.Get(), &SWidget::IsHovered))
];
}
if(PendingRenameTrackIndex == TrackIndex)
{
TWeakPtr<SInlineEditableTextBlock> WeakInlineEditableTextBlock = InlineEditableTextBlock;
InlineEditableTextBlock->RegisterActiveTimer(0.0f, FWidgetActiveTimerDelegate::CreateSP(this, &FAnimTimelineTrack_NotifiesPanel::HandlePendingRenameTimer, WeakInlineEditableTextBlock));
}
TrackIndex++;
}
}
EActiveTimerReturnType FAnimTimelineTrack_NotifiesPanel::HandlePendingRenameTimer(double InCurrentTime, float InDeltaTime, TWeakPtr<SInlineEditableTextBlock> InInlineEditableTextBlock)
{
if(InInlineEditableTextBlock.IsValid())
{
InInlineEditableTextBlock.Pin()->EnterEditingMode();
}
PendingRenameTrackIndex = INDEX_NONE;
return EActiveTimerReturnType::Stop;
}
TSharedRef<SWidget> FAnimTimelineTrack_NotifiesPanel::BuildNotifiesPanelSubMenu(int32 InTrackIndex)
{
UAnimSequenceBase* AnimSequence = GetModel()->GetAnimSequenceBase();
FMenuBuilder MenuBuilder(true, GetModel()->GetCommandList());
MenuBuilder.BeginSection("NotifyTrack", LOCTEXT("NotifyTrackMenuSection", "Notify Track"));
{
MenuBuilder.AddMenuEntry(
FAnimSequenceTimelineCommands::Get().InsertNotifyTrack->GetLabel(),
FAnimSequenceTimelineCommands::Get().InsertNotifyTrack->GetDescription(),
FAnimSequenceTimelineCommands::Get().InsertNotifyTrack->GetIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &FAnimTimelineTrack_NotifiesPanel::InsertTrack, InTrackIndex)
)
);
if(AnimSequence->AnimNotifyTracks.Num() > 1)
{
MenuBuilder.AddMenuEntry(
FAnimSequenceTimelineCommands::Get().RemoveNotifyTrack->GetLabel(),
FAnimSequenceTimelineCommands::Get().RemoveNotifyTrack->GetDescription(),
FAnimSequenceTimelineCommands::Get().RemoveNotifyTrack->GetIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &FAnimTimelineTrack_NotifiesPanel::RemoveTrack, InTrackIndex)
)
);
}
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
void FAnimTimelineTrack_NotifiesPanel::InsertTrack(int32 InTrackIndexToInsert)
{
UAnimSequenceBase* AnimSequence = GetModel()->GetAnimSequenceBase();
FScopedTransaction Transaction(LOCTEXT("InsertNotifyTrack", "Insert Notify Track"));
AnimSequence->Modify();
// before insert, make sure everything behind is fixed
for (int32 TrackIndex = InTrackIndexToInsert; TrackIndex < AnimSequence->AnimNotifyTracks.Num(); ++TrackIndex)
{
FAnimNotifyTrack& Track = AnimSequence->AnimNotifyTracks[TrackIndex];
const int32 NewTrackIndex = TrackIndex + 1;
for (FAnimNotifyEvent* Notify : Track.Notifies)
{
// fix notifies indices
Notify->TrackIndex = NewTrackIndex;
}
for (FAnimSyncMarker* SyncMarker : Track.SyncMarkers)
{
// fix notifies indices
SyncMarker->TrackIndex = NewTrackIndex;
}
}
FAnimNotifyTrack NewItem;
NewItem.TrackName = FAnimTimelineTrack_Notifies::GetNewTrackName(AnimSequence);
NewItem.TrackColor = FLinearColor::White;
AnimSequence->AnimNotifyTracks.Insert(NewItem, InTrackIndexToInsert);
// Request a rename on rebuild
PendingRenameTrackIndex = InTrackIndexToInsert;
Update();
}
void FAnimTimelineTrack_NotifiesPanel::RemoveTrack(int32 InTrackIndexToRemove)
{
UAnimSequenceBase* AnimSequence = GetModel()->GetAnimSequenceBase();
if (AnimSequence->AnimNotifyTracks.IsValidIndex(InTrackIndexToRemove))
{
FScopedTransaction Transaction(LOCTEXT("RemoveNotifyTrack", "Remove Notify Track"));
AnimSequence->Modify();
// before insert, make sure everything behind is fixed
for (int32 TrackIndex = InTrackIndexToRemove; TrackIndex < AnimSequence->AnimNotifyTracks.Num(); ++TrackIndex)
{
FAnimNotifyTrack& Track = AnimSequence->AnimNotifyTracks[TrackIndex];
const int32 NewTrackIndex = FMath::Max(0, TrackIndex - 1);
for (FAnimNotifyEvent* Notify : Track.Notifies)
{
// fix notifies indices
Notify->TrackIndex = NewTrackIndex;
}
for (FAnimSyncMarker* SyncMarker : Track.SyncMarkers)
{
// fix notifies indices
SyncMarker->TrackIndex = NewTrackIndex;
}
}
AnimSequence->AnimNotifyTracks.RemoveAt(InTrackIndexToRemove);
Update();
}
}
void FAnimTimelineTrack_NotifiesPanel::Update()
{
SetHeight((float)GetModel()->GetAnimSequenceBase()->AnimNotifyTracks.Num() * NotificationTrackHeight);
RefreshOutlinerWidget();
if(AnimNotifyPanel.IsValid())
{
AnimNotifyPanel->Update();
}
}
void FAnimTimelineTrack_NotifiesPanel::HandleNotifyChanged()
{
SetHeight((float)GetModel()->GetAnimSequenceBase()->AnimNotifyTracks.Num() * NotificationTrackHeight);
RefreshOutlinerWidget();
}
void FAnimTimelineTrack_NotifiesPanel::OnCommitTrackName(const FText& InText, ETextCommit::Type CommitInfo, int32 TrackIndexToName)
{
UAnimSequenceBase* AnimSequence = GetModel()->GetAnimSequenceBase();
if (AnimSequence->AnimNotifyTracks.IsValidIndex(TrackIndexToName))
{
FScopedTransaction Transaction(FText::Format(LOCTEXT("RenameNotifyTrack", "Rename Notify Track to '{0}'"), InText));
AnimSequence->Modify();
FText TrimText = FText::TrimPrecedingAndTrailing(InText);
AnimSequence->AnimNotifyTracks[TrackIndexToName].TrackName = FName(*TrimText.ToString());
}
}
EVisibility FAnimTimelineTrack_NotifiesPanel::OnGetTimingNodeVisibility(ETimingElementType::Type ElementType) const
{
return StaticCastSharedRef<FAnimModel_AnimSequenceBase>(GetModel())->IsNotifiesTimingElementDisplayEnabled(ElementType) ? EVisibility::Visible : EVisibility::Hidden;
}
TSharedRef<SAnimNotifyPanel> FAnimTimelineTrack_NotifiesPanel::GetAnimNotifyPanel()
{
if(!AnimNotifyPanel.IsValid())
{
UAnimMontage* AnimMontage = Cast<UAnimMontage>(GetModel()->GetAnimSequenceBase());
bool bChildAnimMontage = AnimMontage && AnimMontage->HasParentAsset();
AnimNotifyPanel = SNew(SAnimNotifyPanel, GetModel())
.IsEnabled(!bChildAnimMontage)
.Sequence(GetModel()->GetAnimSequenceBase())
.InputMin(this, &FAnimTimelineTrack_NotifiesPanel::GetMinInput)
.InputMax(this, &FAnimTimelineTrack_NotifiesPanel::GetMaxInput)
.ViewInputMin(this, &FAnimTimelineTrack_NotifiesPanel::GetViewMinInput)
.ViewInputMax(this, &FAnimTimelineTrack_NotifiesPanel::GetViewMaxInput)
.OnGetScrubValue(this, &FAnimTimelineTrack_NotifiesPanel::GetScrubValue)
.OnSelectionChanged(this, &FAnimTimelineTrack_NotifiesPanel::SelectObjects)
.OnSetInputViewRange(this, &FAnimTimelineTrack_NotifiesPanel::OnSetInputViewRange)
.OnInvokeTab(GetModel()->OnInvokeTab)
.OnSnapPosition(&GetModel().Get(), &FAnimModel::Snap)
.OnGetTimingNodeVisibility(this, &FAnimTimelineTrack_NotifiesPanel::OnGetTimingNodeVisibility)
.OnNotifiesChanged_Lambda([this]()
{
Update();
GetModel()->OnTracksChanged().Broadcast();
if (StatusBarMessageHandle.IsValid())
{
if(UStatusBarSubsystem* StatusBarSubsystem = GEditor->GetEditorSubsystem<UStatusBarSubsystem>())
{
StatusBarSubsystem->PopStatusBarMessage(AnimationEditorStatusBarName, StatusBarMessageHandle);
StatusBarMessageHandle.Reset();
}
}
})
.OnNotifyStateHandleBeingDragged_Lambda([this](TSharedPtr<SAnimNotifyNode> NotifyNode, const FPointerEvent& Event, ENotifyStateHandleHit::Type Handle, float Time)
{
if (Event.IsShiftDown())
{
const FFrameTime FrameTime = FFrameTime::FromDecimal(Time * (double)GetModel()->GetTickResolution());
GetModel()->SetScrubPosition(FrameTime);
}
if (!StatusBarMessageHandle.IsValid())
{
if (UStatusBarSubsystem* StatusBarSubsystem = GEditor->GetEditorSubsystem<UStatusBarSubsystem>())
{
StatusBarMessageHandle = StatusBarSubsystem->PushStatusBarMessage(AnimationEditorStatusBarName,
LOCTEXT("AutoscrubNotifyStateHandle", "Hold SHIFT while dragging a notify state Begin or End handle to auto scrub the timeline."));
}
}
})
.OnNotifyNodesBeingDragged_Lambda([this](const TArray<TSharedPtr<SAnimNotifyNode>>& NotifyNodes, const class FDragDropEvent& Event, float DragXPosition, float DragTime)
{
if (Event.IsShiftDown())
{
const FFrameTime FrameTime = FFrameTime::FromDecimal(DragTime * (double)GetModel()->GetTickResolution());
GetModel()->SetScrubPosition(FrameTime);
}
if (!StatusBarMessageHandle.IsValid())
{
if (UStatusBarSubsystem* StatusBarSubsystem = GEditor->GetEditorSubsystem<UStatusBarSubsystem>())
{
StatusBarMessageHandle = StatusBarSubsystem->PushStatusBarMessage(AnimationEditorStatusBarName,
LOCTEXT("AutoscrubNotify", "Hold SHIFT while dragging a notify to auto scrub the timeline."));
}
}
});
GetModel()->GetAnimSequenceBase()->RegisterOnNotifyChanged(UAnimSequenceBase::FOnNotifyChanged::CreateSP(this, &FAnimTimelineTrack_NotifiesPanel::HandleNotifyChanged));
}
return AnimNotifyPanel.ToSharedRef();
}
#undef LOCTEXT_NAMESPACE