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

406 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MovieSceneTrackEditor.h"
#include "CoreMinimal.h"
#include "Misc/Guid.h"
#include "Templates/SubclassOf.h"
#include "ISequencer.h"
#include "Framework/Commands/UICommandList.h"
#include "ScopedTransaction.h"
#include "MovieSceneTrack.h"
#include "ISequencerTrackEditor.h"
#include "MovieScene.h"
#include "MovieSceneSequence.h"
#include "Sequencer.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "SequencerUtilities.h"
#include "MVVM/Views/ViewUtilities.h"
#include "Framework/Application/SlateApplication.h"
#include "MVVM/ViewModels/OutlinerColumns/OutlinerColumnTypes.h"
#include "MVVM/Views/SSequencerKeyNavigationButtons.h"
#include "MVVM/Views/SOutlinerTrackColorPicker.h"
#include "MVVM/Views/SOutlinerItemViewBase.h"
#include "SKeyAreaEditorSwitcher.h"
namespace UE::Sequencer
{
bool HasKeyableAreas(const FViewModel& ViewModel)
{
for (TSharedPtr<FChannelGroupModel> ChannelGroup : ViewModel.GetDescendantsOfType<FChannelGroupModel>())
{
for (const TWeakViewModelPtr<FChannelModel>& WeakChannel : ChannelGroup->GetChannels())
{
if (TViewModelPtr<FChannelModel> Channel = WeakChannel.Pin())
{
if (Channel->GetKeyArea())
{
return true;
}
}
}
}
return false;
}
}
FMovieSceneTrackEditor::FMovieSceneTrackEditor(TSharedRef<ISequencer> InSequencer)
: WeakSequencerPtr(InSequencer)
{
}
FMovieSceneTrackEditor::~FMovieSceneTrackEditor()
{
}
UMovieSceneSequence* FMovieSceneTrackEditor::GetMovieSceneSequence() const
{
return WeakSequencerPtr.Pin()->GetFocusedMovieSceneSequence();
}
TOptional<FFrameNumber> FMovieSceneTrackEditor::NextKeyTime;
bool FMovieSceneTrackEditor::bKeying;
FFrameNumber FMovieSceneTrackEditor::GetTimeForKey()
{
if (bKeying && NextKeyTime.IsSet())
{
return NextKeyTime.GetValue();
}
// Otherwise, key at the current time
TSharedPtr<ISequencer> SequencerPin = WeakSequencerPtr.Pin();
return SequencerPin.IsValid() ? SequencerPin->GetLocalTime().Time.FrameNumber : FFrameNumber(0);
}
void FMovieSceneTrackEditor::BeginKeying(FFrameNumber InFrameNumber)
{
bKeying = true;
NextKeyTime = InFrameNumber;
}
void FMovieSceneTrackEditor::EndKeying()
{
bKeying = false;
NextKeyTime.Reset();
}
void FMovieSceneTrackEditor::UpdatePlaybackRange()
{
TSharedPtr<ISequencer> SequencerPin = WeakSequencerPtr.Pin();
if( SequencerPin.IsValid() )
{
SequencerPin->UpdatePlaybackRange();
}
}
TSharedRef<ISequencerSection> FMovieSceneTrackEditor::MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding)
{
return MakeShared<FSequencerSection>(SectionObject);
}
void FMovieSceneTrackEditor::AnimatablePropertyChanged( FOnKeyProperty OnKeyProperty )
{
check(OnKeyProperty.IsBound());
// Get the movie scene we want to autokey
UMovieSceneSequence* MovieSceneSequence = GetMovieSceneSequence();
if (MovieSceneSequence)
{
FFrameNumber KeyTime = GetTimeForKey();
// @todo Sequencer - The sequencer probably should have taken care of this
MovieSceneSequence->SetFlags(RF_Transactional);
// Create a transaction record because we are about to add keys
const bool bShouldActuallyTransact = !GIsTransacting; // Don't transact if we're recording in a PIE world. That type of keyframe capture cannot be undone.
FScopedTransaction AutoKeyTransaction( NSLOCTEXT("AnimatablePropertyTool", "PropertyChanged", "Animatable Property Changed"), bShouldActuallyTransact );
FKeyPropertyResult KeyPropertyResult = OnKeyProperty.Execute( KeyTime );
// When shift is down, enable adding keys/sections one after the other
const bool bInProperSlateThread = (IsInGameThread() || IsInSlateThread() || IsInAsyncLoadingThread());
if (bInProperSlateThread && FSlateApplication::Get().GetModifierKeys().IsShiftDown())
{
for (TWeakObjectPtr<UMovieSceneSection> NewSection : KeyPropertyResult.SectionsCreated)
{
if (NewSection.IsValid() && NewSection.Get()->HasEndFrame())
{
NextKeyTime = NewSection.Get()->GetExclusiveEndFrame();
break;
}
}
}
if (KeyPropertyResult.bTrackCreated)
{
// If a track is created evaluate immediately so that the pre-animated state can be stored
WeakSequencerPtr.Pin()->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::RefreshAllImmediately );
}
else if (KeyPropertyResult.bTrackModified)
{
WeakSequencerPtr.Pin()->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemAdded );
}
else if (KeyPropertyResult.bKeyCreated)
{
WeakSequencerPtr.Pin()->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
else
{
// If the only thing we changed as a result of the external change were channel defaults, we suppress automatic
// re-evaluation of the sequence for this change to ensure that the object does not have the change immediately overwritten
// by animated channels that have keys, but did not have keys added
UMovieSceneSequence* FocusedSequence = WeakSequencerPtr.Pin()->GetFocusedMovieSceneSequence();
if (FocusedSequence)
{
WeakSequencerPtr.Pin()->SuppressAutoEvaluation(FocusedSequence, FocusedSequence->GetSignature());
}
}
// If a handle was created, something new has been added to Sequencer, so its selection state must be synchronized
if (KeyPropertyResult.bHandleCreated)
{
WeakSequencerPtr.Pin()->ExternalSelectionHasChanged();
}
UpdatePlaybackRange();
}
}
FMovieSceneTrackEditor::FFindOrCreateHandleResult FMovieSceneTrackEditor::FindOrCreateHandleToObject( UObject* Object, bool bCreateHandleIfMissing, const FName& CreatedFolderName)
{
FFindOrCreateHandleResult Result;
bool bHandleWasValid = GetSequencer()->GetHandleToObject( Object, false ).IsValid();
Result.Handle = GetSequencer()->GetHandleToObject( Object, bCreateHandleIfMissing, CreatedFolderName);
Result.bWasCreated = bHandleWasValid == false && Result.Handle.IsValid();
return Result;
}
FMovieSceneTrackEditor::FFindOrCreateTrackResult FMovieSceneTrackEditor::FindOrCreateTrackForObject( const FGuid& ObjectHandle, TSubclassOf<UMovieSceneTrack> TrackClass, FName PropertyName, bool bCreateTrackIfMissing )
{
FFindOrCreateTrackResult Result;
bool bTrackExisted;
UMovieScene* MovieScene = GetSequencer()->GetFocusedMovieSceneSequence()->GetMovieScene();
Result.Track = MovieScene->FindTrack( TrackClass, ObjectHandle, PropertyName );
bTrackExisted = Result.Track != nullptr;
if (!Result.Track && bCreateTrackIfMissing)
{
Result.Track = AddTrack(MovieScene, ObjectHandle, TrackClass, PropertyName);
}
Result.bWasCreated = bTrackExisted == false && Result.Track != nullptr;
return Result;
}
TSharedPtr<ISequencer> FMovieSceneTrackEditor::GetSequencer() const
{
return WeakSequencerPtr.IsValid() ? WeakSequencerPtr.Pin() : nullptr;
}
FText FMovieSceneTrackEditor::GetDisplayName() const
{
return FText::GetEmpty();
}
UMovieSceneTrack* FMovieSceneTrackEditor::AddTrack(UMovieScene* FocusedMovieScene, const FGuid& ObjectHandle, TSubclassOf<class UMovieSceneTrack> TrackClass, FName UniqueTypeName)
{
return FocusedMovieScene->AddTrack(TrackClass, ObjectHandle);
}
void FMovieSceneTrackEditor::BindCommands(TSharedRef<FUICommandList> SequencerCommandBindings)
{
}
void FMovieSceneTrackEditor::BuildPinnedAddTrackMenu(FMenuBuilder& MenuBuilder)
{
}
void FMovieSceneTrackEditor::BuildAddTrackMenu(FMenuBuilder& MenuBuilder)
{
}
void FMovieSceneTrackEditor::BuildObjectBindingColumnWidgets(TFunctionRef<TSharedRef<SHorizontalBox>()> GetEditBox, const UE::Sequencer::TViewModelPtr<UE::Sequencer::FObjectBindingModel>& ObjectBinding, const UE::Sequencer::FCreateOutlinerViewParams& InParams, const FName& InColumnName)
{
}
void FMovieSceneTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray<FGuid>& ObjectBindings, const UClass* ObjectClass)
{
}
TSharedPtr<SWidget> FMovieSceneTrackEditor::BuildOutlinerColumnWidget(const FBuildColumnWidgetParams& Params, const FName& ColumnName)
{
using namespace UE::Sequencer;
UMovieSceneTrack* Track = Params.TrackModel->GetTrack();
TViewModelPtr<FSequencerEditorViewModel> Editor = Params.Editor->CastThisShared<FSequencerEditorViewModel>();
TViewModelPtr<IOutlinerExtension> OutlinerItem = Params.ViewModel.ImplicitCast();
if (!Track || !Editor || !OutlinerItem)
{
return SNullWidget::NullWidget;
}
if (ColumnName == FCommonOutlinerNames::Label)
{
return SNew(SOutlinerItemViewBase, OutlinerItem, Params.Editor, Params.TreeViewRow);
}
if (ColumnName == FCommonOutlinerNames::Edit)
{
TOptional<FViewModelChildren> TopLevelChannels = Params.ViewModel->FindChildList(FTrackModel::GetTopLevelChannelGroupType());
TSharedPtr<FChannelGroupModel> TopLevelChannel = TopLevelChannels ? TopLevelChannels->FindFirstChildOfType<FChannelGroupModel>() : nullptr;
if (TopLevelChannel)
{
return SNew(SKeyAreaEditorSwitcher, TopLevelChannel, Editor);
}
TAttribute<bool> IsHovered = MakeAttributeSP(Params.TreeViewRow, &ISequencerTreeViewRow::IsHovered);
FBuildEditWidgetParams EditWidgetParams(Params);
EditWidgetParams.NodeIsHovered = IsHovered;
EditWidgetParams.RowIndex = Params.TrackModel->GetRowIndex();
if (Params.ViewModel->FindAncestorOfType<ITrackExtension>())
{
EditWidgetParams.TrackInsertRowIndex = Params.TrackModel->GetRowIndex();
}
else if (Track && Track->SupportsMultipleRows())
{
EditWidgetParams.TrackInsertRowIndex = Track->GetMaxRowIndex() + 1;
}
TViewModelPtr<IObjectBindingExtension> ObjectBinding = Params.ViewModel->FindAncestorOfType<IObjectBindingExtension>();
return BuildOutlinerEditColumnWidget(ObjectBinding ? ObjectBinding->GetObjectGuid() : FGuid(), Track, EditWidgetParams);
}
if (ColumnName == FCommonOutlinerNames::Add)
{
TAttribute<bool> IsHovered = MakeAttributeSP(Params.TreeViewRow, &ISequencerTreeViewRow::IsHovered);
// This function is a misnomer but is called this way for backwards compatibility
FBuildEditWidgetParams EditWidgetParams(Params);
EditWidgetParams.NodeIsHovered = IsHovered;
EditWidgetParams.RowIndex = Params.TrackModel->GetRowIndex();
if (Params.ViewModel->FindAncestorOfType<ITrackExtension>())
{
EditWidgetParams.TrackInsertRowIndex = Params.TrackModel->GetRowIndex();
}
else if (Track && Track->SupportsMultipleRows())
{
EditWidgetParams.TrackInsertRowIndex = Track->GetMaxRowIndex() + 1;
}
TViewModelPtr<IObjectBindingExtension> ObjectBinding = Params.ViewModel->FindAncestorOfType<IObjectBindingExtension>();
return BuildOutlinerEditWidget(ObjectBinding ? ObjectBinding->GetObjectGuid() : FGuid(), Track, EditWidgetParams);
}
if (ColumnName == FCommonOutlinerNames::KeyFrame)
{
if (HasKeyableAreas(*Params.ViewModel))
{
EKeyNavigationButtons Buttons = EKeyNavigationButtons::AddKey;
return SNew(SSequencerKeyNavigationButtons, Params.ViewModel, Editor->GetSequencer())
.Buttons(Buttons);
}
return nullptr;
}
if (ColumnName == FCommonOutlinerNames::Nav)
{
if (HasKeyableAreas(*Params.ViewModel))
{
EKeyNavigationButtons Buttons = Params.TreeViewRow->IsColumnVisible(FCommonOutlinerNames::KeyFrame)
? EKeyNavigationButtons::NavOnly
: EKeyNavigationButtons::All;
return SNew(SSequencerKeyNavigationButtons, Params.ViewModel, Editor->GetSequencer())
.Buttons(Buttons);
}
return nullptr;
}
if (ColumnName == FCommonOutlinerNames::ColorPicker)
{
return SNew(SOutlinerTrackColorPicker, Params.ViewModel.ImplicitCast(), Params.Editor);
}
return nullptr;
}
TSharedPtr<SWidget> FMovieSceneTrackEditor::BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params)
{
if (Track && Track->GetSupportedBlendTypes().Num() > 0)
{
TWeakPtr<ISequencer> WeakSequencer = GetSequencer();
const int32 RowIndex = Params.TrackInsertRowIndex;
auto SubMenuCallback = [=]() -> TSharedRef<SWidget>
{
FMenuBuilder MenuBuilder(true, nullptr);
FSequencerUtilities::PopulateMenu_CreateNewSection(MenuBuilder, RowIndex, Track, WeakSequencer);
return MenuBuilder.MakeWidget();
};
return UE::Sequencer::MakeAddButton(NSLOCTEXT("MovieSceneTrackEditor", "AddSection", "Section"), FOnGetContent::CreateLambda(SubMenuCallback), Params.ViewModel);
}
else
{
return TSharedPtr<SWidget>();
}
}
TSharedPtr<SWidget> FMovieSceneTrackEditor::BuildOutlinerEditColumnWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params)
{
return TSharedPtr<SWidget>();
}
void FMovieSceneTrackEditor::BuildTrackContextMenu(FMenuBuilder& MenuBuilder, UMovieSceneTrack* Track)
{
}
void FMovieSceneTrackEditor::BuildTrackSidebarMenu(FMenuBuilder& MenuBuilder, UMovieSceneTrack* Track)
{
BuildTrackContextMenu(MenuBuilder, Track);
}
bool FMovieSceneTrackEditor::HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid)
{
return false;
}
bool FMovieSceneTrackEditor::OnAllowDrop(const FDragDropEvent& DragDropEvent, FSequencerDragDropParams& DragDropParams)
{
return false;
}
FReply FMovieSceneTrackEditor::OnDrop(const FDragDropEvent& DragDropEvent, const FSequencerDragDropParams& DragDropParams)
{
return FReply::Unhandled();
}
void FMovieSceneTrackEditor::OnInitialize()
{
}
void FMovieSceneTrackEditor::OnRelease()
{
}
void FMovieSceneTrackEditor::Tick(float DeltaTime)
{
}
UMovieScene* FMovieSceneTrackEditor::GetFocusedMovieScene() const
{
UMovieSceneSequence* FocusedSequence = GetSequencer()->GetFocusedMovieSceneSequence();
return FocusedSequence->GetMovieScene();
}