Files
UnrealEngine/Engine/Source/Editor/Sequencer/Private/MVVM/ViewModels/SequenceModel.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

444 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MVVM/ViewModels/SequenceModel.h"
#include "MVVM/Extensions/IObjectBindingExtension.h"
#include "MVVM/Extensions/IObjectModelExtension.h"
#include "MVVM/Extensions/ILockableExtension.h"
#include "MVVM/Extensions/IMutableExtension.h"
#include "MVVM/Extensions/ISoloableExtension.h"
#include "MVVM/ViewModels/FolderModel.h"
#include "MVVM/ViewModels/OutlinerItemModel.h"
#include "MVVM/ViewModels/TrackModel.h"
#include "MVVM/SharedViewModelData.h"
#include "MVVM/ViewModels/OutlinerSpacer.h"
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
#include "MVVM/ViewModels/ViewModel.h"
#include "MVVM/ViewModels/ViewModelIterators.h"
#include "MVVM/ViewModels/EditorSharedViewModelData.h"
#include "MovieScene.h"
#include "MovieSceneFolder.h"
#include "MovieSceneSequence.h"
#include "Sequencer.h"
#include "SequencerOutlinerItemDragDropOp.h"
#include "ISequencerCoreModule.h"
#include "ScopedTransaction.h"
#define LOCTEXT_NAMESPACE "FSequenceModel"
namespace UE
{
namespace Sequencer
{
FOnInitializeSequenceModel FSequenceModel::CreateExtensionsEvent;
FSequenceModel::FSequenceModel(TWeakPtr<FSequencerEditorViewModel> InEditorViewModel)
: RootOutlinerItems(EViewModelListType::Outliner)
, DecorationModelList(GetDecorationModelListType())
{
WeakEditor = InEditorViewModel;
RegisterChildList(&RootOutlinerItems);
RegisterChildList(&DecorationModelList);
TSharedPtr<FSharedViewModelData> NewSharedData = MakeShared<FEditorSharedViewModelData>(WeakEditor.Pin().ToSharedRef());
SetSharedDataFromConstructor(NewSharedData);
}
EViewModelListType FSequenceModel::GetDecorationModelListType()
{
static EViewModelListType DecorationModelList = RegisterCustomModelListType();
return DecorationModelList;
}
void FSequenceModel::InitializeExtensions()
{
TSharedPtr<FSharedViewModelData> ThisSharedData = GetSharedData();
// Re-generate hierarchical caches when the sequence changes
ThisSharedData->AddDynamicExtension(FOutlinerCacheExtension::ID);
ThisSharedData->AddDynamicExtension(FDeactiveStateCacheExtension::ID);
ThisSharedData->AddDynamicExtension(FMuteStateCacheExtension::ID);
ThisSharedData->AddDynamicExtension(FSoloStateCacheExtension::ID);
ThisSharedData->AddDynamicExtension(FLockStateCacheExtension::ID);
ThisSharedData->AddDynamicExtension(FConditionStateCacheExtension::ID);
// Add our hierarchical cache processor
TSharedPtr<FOutlinerCacheExtension> OutlinerCache = ThisSharedData->CastThisSharedChecked<FOutlinerCacheExtension>();
OutlinerCache->Initialize(AsShared());
// Make sure the hierarchical cache is updated when our hierarchy changes
FSimpleMulticastDelegate& HierarchyChanged = ThisSharedData->SubscribeToHierarchyChanged(AsShared());
HierarchyChanged.AddSP(OutlinerCache.ToSharedRef(), &FOutlinerCacheExtension::OnHierarchyUpdated);
// This needs to be run outside of the constructor because a shared pointer to 'this' can't
// be created until after the object is fully built.
// Create the bottom spacer item
BottomSpacer = MakeShared<FOutlinerSpacer>(30.f);
GetChildrenForList(&RootOutlinerItems).AddChild(BottomSpacer);
// Create extensions
TSharedPtr<FSequencerEditorViewModel> Editor = WeakEditor.Pin();
check(Editor);
CreateExtensionsEvent.Broadcast(Editor, SharedThis(this));
// Post-initialize extensions - this does the bulk of the population work for the sequence model
PostInitializeExtensions();
}
void FSequenceModel::OnDecorationAdded(UObject* AddedDecoration)
{
static ISequencerCoreModule& SequencerCore = FModuleManager::Get().LoadModuleChecked<ISequencerCoreModule>("SequencerCore");
TSharedPtr<FViewModel> NewModel = SequencerCore.FactoryNewModel(AddedDecoration);
if (NewModel && NewModel->IsA<IObjectModelExtension>())
{
GetChildrenForList(&DecorationModelList).AddChild(NewModel);
}
}
void FSequenceModel::OnDecorationRemoved(UObject* RemovedDecoration)
{
for (TViewModelPtr<IObjectModelExtension> Object : GetChildrenForList(&DecorationModelList).IterateSubList<IObjectModelExtension>())
{
if (Object->GetObject() == RemovedDecoration)
{
Object.AsModel()->RemoveFromParent();
break;
}
}
}
FMovieSceneSequenceID FSequenceModel::GetSequenceID() const
{
return SequenceID;
}
UMovieSceneSequence* FSequenceModel::GetSequence() const
{
return WeakSequence.Get();
}
UMovieScene* FSequenceModel::GetMovieScene() const
{
UMovieSceneSequence* Sequence = WeakSequence.Get();
return Sequence ? Sequence->GetMovieScene() : nullptr;
}
void FSequenceModel::SetSequence(UMovieSceneSequence* InSequence, FMovieSceneSequenceID InSequenceID)
{
if (WeakSequence.Get() != InSequence || SequenceID != InSequenceID)
{
FViewModelHierarchyOperation HierarchyOperation(GetSharedData());
// Remove everything and re-add the bottom spacer node
DiscardAllChildren();
if (BottomSpacer)
{
GetChildrenForList(&RootOutlinerItems).AddChild(BottomSpacer);
}
WeakSequence = InSequence;
SequenceID = InSequenceID;
SequenceEventHandler.Unlink();
MovieSceneEventHandler.Unlink();
MovieSceneDataEventHandler.Unlink();
if (InSequence)
{
InSequence->EventHandlers.Link(SequenceEventHandler, this);
InSequence->GetMovieScene()->UMovieSceneSignedObject::EventHandlers.Link(MovieSceneEventHandler, this);
InSequence->GetMovieScene()->EventHandlers.Link(MovieSceneDataEventHandler, this);
}
ReinitializeExtensions();
GetChildrenForList(&DecorationModelList).Empty();
if (InSequence)
{
for (UObject* Decoration : InSequence->GetMovieScene()->GetDecorations())
{
OnDecorationAdded(Decoration);
}
}
}
}
void FSequenceModel::OnPostUndo()
{
{
FViewModelHierarchyOperation HierarchyOp(GetSharedData());
for (TSharedPtr<FViewModel> Child : GetChildren().ToArray())
{
Child->RemoveFromParent();
}
}
ReinitializeExtensions();
}
TSharedPtr<FSequencerEditorViewModel> FSequenceModel::GetEditor() const
{
return WeakEditor.Pin();
}
TSharedPtr<ISequencer> FSequenceModel::GetSequencer() const
{
return GetEditor()->GetSequencer();
}
TSharedPtr<FSequencer> FSequenceModel::GetSequencerImpl() const
{
return GetEditor()->GetSequencerImpl();
}
void FSequenceModel::SortChildren()
{
ISortableExtension::SortChildren(SharedThis(this), ESortingMode::Default);
}
FSortingKey FSequenceModel::GetSortingKey() const
{
return FSortingKey();
}
void FSequenceModel::SetCustomOrder(int32 InCustomOrder)
{
}
TOptional<EItemDropZone> FSequenceModel::CanAcceptDrop(const FViewModelPtr& TargetModel, const FDragDropEvent& DragDropEvent, EItemDropZone ItemDropZone)
{
TSharedPtr<FSequencerOutlinerDragDropOp> DragDropOp = DragDropEvent.GetOperationAs<FSequencerOutlinerDragDropOp>();
if (!DragDropOp)
{
return TOptional<EItemDropZone>();
}
DragDropOp->ResetToDefaultToolTip();
if (TargetModel == BottomSpacer)
{
// Dropping on the bottom spacer
return EItemDropZone::AboveItem;
}
for (const TWeakViewModelPtr<IOutlinerExtension>& WeakModel : DragDropOp->GetDraggedViewModels())
{
FViewModelPtr Model = WeakModel.Pin();
if (!Model)
{
// Silently allow null models
continue;
}
if (Model.Get() == this || Model == TargetModel)
{
// Cannot drop onto self
return TOptional<EItemDropZone>();
}
if (Model->IsA<FFolderModel>())
{
// We can always drop folders at the root
}
else if (TViewModelPtr<IObjectBindingExtension> Object = Model.ImplicitCast())
{
if (TViewModelPtr<IObjectBindingExtension> ParentObject = Model->CastParent<IObjectBindingExtension>())
{
// This operation should not have been allowed in the first place (child objects should never be draggable)
return TOptional<EItemDropZone>();
}
}
else if (TViewModelPtr<FTrackModel> Track = Model.ImplicitCast())
{
if (TViewModelPtr<IObjectBindingExtension> ParentObject = Model->CastParent<IObjectBindingExtension>())
{
// This operation should not have been allowed in the first place (tracks bound to an object should never be draggable)
return TOptional<EItemDropZone>();
}
}
else
{
// Unknown model type - do we silent not drag these or return an error?
// For now we disallow the drag, but maybe that's not the right choice
return TOptional<EItemDropZone>();
}
}
// The dragged nodes were either all in folders, or all at the sequencer root.
return ItemDropZone;
}
void FSequenceModel::PerformDrop(const FViewModelPtr& TargetModel, const FDragDropEvent& DragDropEvent, EItemDropZone InItemDropZone)
{
FViewModelHierarchyOperation ScopedOperation(GetSharedData());
UMovieSceneSequence* Sequence = WeakSequence.Get();
UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;;
if (!MovieScene)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("MoveItems", "Move to Root"));
MovieScene->SetFlags(RF_Transactional);
MovieScene->Modify();
TSharedPtr<FViewModel> AttachAfter;
if (TargetModel)
{
if (InItemDropZone == EItemDropZone::AboveItem)
{
AttachAfter = TargetModel->GetPreviousSibling();
}
else if (InItemDropZone == EItemDropZone::BelowItem)
{
AttachAfter = TargetModel;
}
}
TSharedPtr<FSequencerOutlinerDragDropOp> DragDropOp = DragDropEvent.GetOperationAs<FSequencerOutlinerDragDropOp>();
// Drop handing for outliner drag/drop operations.
// Warning: this handler may be dragging nodes from a *different* sequence
if (DragDropOp)
{
FViewModelChildren OutlinerChildren = GetChildList(EViewModelListType::Outliner);
for (const TWeakViewModelPtr<IOutlinerExtension>& WeakModel : DragDropOp->GetDraggedViewModels())
{
FViewModelPtr DraggedModel = WeakModel.Pin();
if (!DraggedModel || DraggedModel == AttachAfter)
{
continue;
}
TViewModelPtr<FFolderModel> ExistingParentFolder = DraggedModel->CastParent<FFolderModel>();
UMovieSceneFolder* OldFolder = ExistingParentFolder ? ExistingParentFolder->GetFolder() : nullptr;
bool bSuccess = false;
// Handle dropping a folder into another folder
// @todo: if we ever support folders within object bindings this will need to have better validation
if (TSharedPtr<FFolderModel> DraggedFolderModel = DraggedModel.ImplicitCast())
{
if (UMovieSceneFolder* DraggedFolder = DraggedFolderModel->GetFolder())
{
if (OldFolder)
{
OldFolder->SetFlags(RF_Transactional);
OldFolder->Modify();
OldFolder->RemoveChildFolder(DraggedFolder);
}
// Give this folder a unique name inside its new parent if necessary
TArray<UMovieSceneFolder*> ExistingFolders;
MovieScene->GetRootFolders(ExistingFolders);
ExistingFolders.Remove(DraggedFolder);
FName FolderName = UMovieSceneFolder::MakeUniqueChildFolderName(DraggedFolder->GetFolderName(), ExistingFolders);
if (FolderName != DraggedFolder->GetFolderName())
{
DraggedFolder->SetFlags(RF_Transactional);
DraggedFolder->Modify();
DraggedFolder->SetFolderName(FolderName);
}
// MovieScene has already had Modify called above
MovieScene->AddRootFolder(DraggedFolder);
bSuccess = true;
}
}
else if (TSharedPtr<IObjectBindingExtension> ObjectBinding = DraggedModel.ImplicitCast())
{
TViewModelPtr<IObjectBindingExtension> ParentObject = DraggedModel->CastParent<IObjectBindingExtension>();
// Don't allow dropping an object binding if it has an object parent
if (ParentObject == nullptr)
{
if (OldFolder)
{
OldFolder->SetFlags(RF_Transactional);
OldFolder->Modify();
OldFolder->RemoveChildObjectBinding(ObjectBinding->GetObjectGuid());
}
bSuccess = true;
}
}
else if (TSharedPtr<FTrackModel> TrackModel = DraggedModel.ImplicitCast())
{
UMovieSceneTrack* Track = TrackModel->GetTrack();
TViewModelPtr<IObjectBindingExtension> ParentObject = DraggedModel->CastParent<IObjectBindingExtension>();
// Don't allow dropping a track if it has an object parent
if (ParentObject == nullptr && Track != nullptr)
{
if (OldFolder)
{
OldFolder->SetFlags(RF_Transactional);
OldFolder->Modify();
OldFolder->RemoveChildTrack(Track);
}
bSuccess = true;
}
}
if (bSuccess)
{
// Attach it to the right node, possibly setting its parent too
OutlinerChildren.InsertChild(DraggedModel, AttachAfter);
}
}
}
// Forcibly update sorting
int32 CustomSort = 0;
for (const TViewModelPtr<ISortableExtension>& Sortable : GetChildrenOfType<ISortableExtension>())
{
Sortable->SetCustomOrder(CustomSort);
++CustomSort;
}
}
void FSequenceModel::OnModifiedIndirectly(UMovieSceneSignedObject*)
{
TSharedPtr<ISequencer> Sequencer = GetEditor()->GetSequencer();
const bool bRecording = Sequencer && Sequencer->OnGetIsRecording().IsBound() && Sequencer->OnGetIsRecording().Execute();
// Disregard updating cached flags during recording
if (!bRecording)
{
if (FOutlinerCacheExtension* OutlinerCache = GetSharedData()->CastThis<FOutlinerCacheExtension>())
{
OutlinerCache->UpdateCachedFlags();
}
}
}
void FSequenceModel::OnModifiedDirectly(UMovieSceneSignedObject*)
{
TSharedPtr<ISequencer> Sequencer = GetEditor()->GetSequencer();
const bool bRecording = Sequencer && Sequencer->OnGetIsRecording().IsBound() && Sequencer->OnGetIsRecording().Execute();
// Disregard updating cached flags during recording
if (!bRecording)
{
if (FOutlinerCacheExtension* OutlinerCache = GetSharedData()->CastThis<FOutlinerCacheExtension>())
{
OutlinerCache->UpdateCachedFlags();
}
}
}
} // namespace Sequencer
} // namespace UE
#undef LOCTEXT_NAMESPACE