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

295 lines
9.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimTimeline/SAnimTimelineTransportControls.h"
#include "EditorWidgetsModule.h"
#include "AnimationEditorPreviewScene.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "Animation/AnimSingleNodeInstance.h"
#include "Animation/AnimSequenceBase.h"
#include "Animation/AnimSequence.h"
#include "AnimPreviewInstance.h"
#include "Modules/ModuleManager.h"
#include "ToolMenu.h"
#include "ToolMenus.h"
#include "UICommandList_Pinnable.h"
#include "ViewportToolbar/AnimViewportContext.h"
#include "ViewportToolbar/AnimationEditorMenus.h"
#include "Widgets/SBoxPanel.h"
void SAnimTimelineTransportControls::Construct(const FArguments& InArgs, const TSharedRef<IPersonaPreviewScene>& InPreviewScene, UAnimSequenceBase* InAnimSequenceBase)
{
WeakPreviewScene = InPreviewScene;
AnimSequenceBase = InAnimSequenceBase;
check(AnimSequenceBase);
FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::LoadModuleChecked<FEditorWidgetsModule>("EditorWidgets");
FTransportControlArgs TransportControlArgs;
TransportControlArgs.OnForwardPlay = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Forward);
TransportControlArgs.OnRecord = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Record);
TransportControlArgs.OnBackwardPlay = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Backward);
TransportControlArgs.OnForwardStep = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Forward_Step);
TransportControlArgs.OnBackwardStep = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Backward_Step);
TransportControlArgs.OnForwardEnd = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Forward_End);
TransportControlArgs.OnBackwardEnd = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_Backward_End);
TransportControlArgs.OnToggleLooping = FOnClicked::CreateSP(this, &SAnimTimelineTransportControls::OnClick_ToggleLoop);
TransportControlArgs.OnGetLooping = FOnGetLooping::CreateSP(this, &SAnimTimelineTransportControls::IsLoopStatusOn);
TransportControlArgs.OnGetPlaybackMode = FOnGetPlaybackMode::CreateSP(this, &SAnimTimelineTransportControls::GetPlaybackMode);
TransportControlArgs.OnGetRecording = FOnGetRecording::CreateSP(this, &SAnimTimelineTransportControls::IsRecording);
TSharedPtr<SHorizontalBox> HorizontalBox;
// clang-format off
ChildSlot
[
SAssignNew(HorizontalBox, SHorizontalBox)
+ SHorizontalBox::Slot()
.HAlign(HAlign_Center)
.FillWidth(1.0)
[
EditorWidgetsModule.CreateTransportControl(TransportControlArgs)
]
];
// clang-format on
// Add Playback Speed widget - we need to retrieve the viewport tab and the viewport it hosts
if (TSharedPtr<FAnimationEditorPreviewScene> PreviewScene =
StaticCastSharedPtr<FAnimationEditorPreviewScene>(WeakPreviewScene.Pin()))
{
const FName TimelinePlaybackMenu = "AnimationEditor.Timeline.PlaybackMenu";
if (!UToolMenus::Get()->IsMenuRegistered(TimelinePlaybackMenu))
{
if (UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(
TimelinePlaybackMenu, NAME_None, EMultiBoxType::SlimHorizontalToolBar, false
))
{
FToolMenuSection& UnnamedSection = Menu->AddSection(NAME_None);
UnnamedSection.AddEntry(UE::AnimationEditor::CreatePlaybackSubmenu());
}
}
FToolMenuContext MenuContext;
{
MenuContext.AppendCommandList(PreviewScene->GetCommandList());
UAnimViewportContext* const ContextObject = NewObject<UAnimViewportContext>();
ContextObject->PersonaPreviewScene = PreviewScene;
MenuContext.AddObject(ContextObject);
}
// clang-format off
HorizontalBox->AddSlot()
.HAlign(HAlign_Right)
.FillWidth(0.4)
[
UToolMenus::Get()->GenerateWidget(TimelinePlaybackMenu, MenuContext)
];
// clang-format on
}
}
UAnimSingleNodeInstance* SAnimTimelineTransportControls::GetPreviewInstance() const
{
UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent();
return PreviewMeshComponent && PreviewMeshComponent->IsPreviewOn()? PreviewMeshComponent->PreviewInstance : nullptr;
}
FReply SAnimTimelineTransportControls::OnClick_Forward_Step()
{
UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent();
if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance())
{
bool bShouldStepCloth = FMath::Abs(PreviewInstance->GetLength() - PreviewInstance->GetCurrentTime()) > SMALL_NUMBER;
PreviewInstance->SetPlaying(false);
PreviewInstance->StepForward();
if(SMC && bShouldStepCloth)
{
SMC->bPerformSingleClothingTick = true;
}
}
else if (SMC)
{
const UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimSequenceBase);
const FFrameRate TargetFramerate = AnimSequence ? AnimSequence->GetSamplingFrameRate() : FFrameRate(30, 1);
// Advance a single frame, leaving it paused afterwards
SMC->GlobalAnimRateScale = 1.0f;
SMC->TickAnimation(static_cast<float>(TargetFramerate.AsInterval()), false);
SMC->GlobalAnimRateScale = 0.0f;
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_Forward_End()
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
if (PreviewInstance)
{
PreviewInstance->SetPlaying(false);
PreviewInstance->SetPosition(PreviewInstance->GetLength(), false);
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_Backward_Step()
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent();
if (PreviewInstance)
{
bool bShouldStepCloth = PreviewInstance->GetCurrentTime() > SMALL_NUMBER;
PreviewInstance->SetPlaying(false);
PreviewInstance->StepBackward();
if(SMC && bShouldStepCloth)
{
SMC->bPerformSingleClothingTick = true;
}
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_Backward_End()
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
if (PreviewInstance)
{
PreviewInstance->SetPlaying(false);
PreviewInstance->SetPosition(0.f, false);
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_Forward()
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent();
if (PreviewInstance)
{
bool bIsReverse = PreviewInstance->IsReverse();
bool bIsPlaying = PreviewInstance->IsPlaying();
// if current bIsReverse and bIsPlaying, we'd like to just turn off reverse
if (bIsReverse && bIsPlaying)
{
PreviewInstance->SetReverse(false);
}
// already playing, simply pause
else if (bIsPlaying)
{
PreviewInstance->SetPlaying(false);
if(SMC && SMC->bPauseClothingSimulationWithAnim)
{
SMC->SuspendClothingSimulation();
}
}
// if not playing, play forward
else
{
//if we're at the end of the animation, jump back to the beginning before playing
if ( PreviewInstance->GetCurrentTime() >= AnimSequenceBase->GetPlayLength() )
{
PreviewInstance->SetPosition(0.0f, false);
}
PreviewInstance->SetReverse(false);
PreviewInstance->SetPlaying(true);
if(SMC && SMC->bPauseClothingSimulationWithAnim)
{
SMC->ResumeClothingSimulation();
}
}
}
else if(SMC)
{
SMC->GlobalAnimRateScale = (SMC->GlobalAnimRateScale > 0.0f) ? 0.0f : 1.0f;
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_Backward()
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
if (PreviewInstance)
{
bool bIsReverse = PreviewInstance->IsReverse();
bool bIsPlaying = PreviewInstance->IsPlaying();
// if currently playing forward, just simply turn on reverse
if (!bIsReverse && bIsPlaying)
{
PreviewInstance->SetReverse(true);
}
else if (bIsPlaying)
{
PreviewInstance->SetPlaying(false);
}
else
{
//if we're at the beginning of the animation, jump back to the end before playing
if ( PreviewInstance->GetCurrentTime() <= 0.0f )
{
PreviewInstance->SetPosition(AnimSequenceBase->GetPlayLength(), false);
}
PreviewInstance->SetPlaying(true);
PreviewInstance->SetReverse(true);
}
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_ToggleLoop()
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
if (PreviewInstance)
{
bool bIsLooping = PreviewInstance->IsLooping();
PreviewInstance->SetLooping(!bIsLooping);
}
return FReply::Handled();
}
FReply SAnimTimelineTransportControls::OnClick_Record()
{
StaticCastSharedRef<FAnimationEditorPreviewScene>(GetPreviewScene())->RecordAnimation();
return FReply::Handled();
}
bool SAnimTimelineTransportControls::IsLoopStatusOn() const
{
UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance();
return (PreviewInstance && PreviewInstance->IsLooping());
}
EPlaybackMode::Type SAnimTimelineTransportControls::GetPlaybackMode() const
{
if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance())
{
if (PreviewInstance->IsPlaying())
{
return PreviewInstance->IsReverse() ? EPlaybackMode::PlayingReverse : EPlaybackMode::PlayingForward;
}
return EPlaybackMode::Stopped;
}
else if (UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent())
{
return (SMC->GlobalAnimRateScale > 0.0f) ? EPlaybackMode::PlayingForward : EPlaybackMode::Stopped;
}
return EPlaybackMode::Stopped;
}
bool SAnimTimelineTransportControls::IsRecording() const
{
return StaticCastSharedRef<FAnimationEditorPreviewScene>(GetPreviewScene())->IsRecording();
}