// 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& InPreviewScene, UAnimSequenceBase* InAnimSequenceBase) { WeakPreviewScene = InPreviewScene; AnimSequenceBase = InAnimSequenceBase; check(AnimSequenceBase); FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::LoadModuleChecked("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 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 PreviewScene = StaticCastSharedPtr(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(); 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(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(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(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(GetPreviewScene())->IsRecording(); }