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

428 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SequencerNodeTree.h"
#include "Filters/SequencerFilterBar.h"
#include "MVVM/ViewModels/ViewModelHierarchy.h"
#include "MVVM/Selection/Selection.h"
#include "MovieSceneBinding.h"
#include "GameFramework/Actor.h"
#include "MovieScene.h"
#include "MVVM/ViewModels/ViewModel.h"
#include "MVVM/Extensions/IOutlinerExtension.h"
#include "MVVM/Extensions/ICurveEditorTreeItemExtension.h"
#include "MVVM/Extensions/IPinnableExtension.h"
#include "MVVM/Extensions/ISoloableExtension.h"
#include "MVVM/Extensions/IMutableExtension.h"
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
#include "MVVM/ViewModels/OutlinerViewModel.h"
#include "MVVM/ViewModels/CategoryModel.h"
#include "MVVM/ViewModels/FolderModel.h"
#include "MVVM/ViewModels/ViewModelIterators.h"
#include "MVVM/ViewModels/ObjectBindingModel.h"
#include "MVVM/ViewModels/OutlinerSpacer.h"
#include "MVVM/ViewModels/SectionModel.h"
#include "MVVM/ViewModels/SequenceModel.h"
#include "MVVM/ViewModels/TrackModel.h"
#include "MVVM/CurveEditorIntegrationExtension.h"
#include "MVVM/ObjectBindingModelStorageExtension.h"
#include "MVVM/SectionModelStorageExtension.h"
#include "MVVM/TrackModelStorageExtension.h"
#include "IKeyArea.h"
#include "ISequencerSection.h"
#include "MovieSceneSequence.h"
#include "Sequencer.h"
#include "MovieSceneFolder.h"
#include "ISequencerTrackEditor.h"
#include "Widgets/Views/STableRow.h"
#include "CurveEditor.h"
#include "Channels/MovieSceneChannel.h"
#include "ScopedTransaction.h"
#include "SequencerUtilities.h"
#include "SequencerLog.h"
#include "SequencerCommonHelpers.h"
#include "SSequencer.h"
FSequencerNodeTree::~FSequencerNodeTree()
{
}
FSequencerNodeTree::FSequencerNodeTree(FSequencer& InSequencer)
: Sequencer(InSequencer)
, bFilterUpdateRequested(false)
{
}
TSharedPtr<UE::Sequencer::FObjectBindingModel> FSequencerNodeTree::FindObjectBindingNode(const FGuid& BindingID) const
{
using namespace UE::Sequencer;
const FObjectBindingModelStorageExtension* ObjectBindingStorage = RootNode->CastDynamic<FObjectBindingModelStorageExtension>();
if (ObjectBindingStorage)
{
TSharedPtr<FObjectBindingModel> Model = ObjectBindingStorage->FindModelForObjectBinding(BindingID);
return Model;
}
return nullptr;
}
bool FSequencerNodeTree::UpdateFiltersOnTrackValueChanged()
{
// If filters are already scheduled for update, we can defer until the next update
if (bFilterUpdateRequested)
{
return false;
}
if (Sequencer.GetFilterBar()->ShouldUpdateOnTrackValueChanged())
{
// UpdateFilters will only run if bFilterUpdateRequested is true
bFilterUpdateRequested = true;
bool bFiltersUpdated = UpdateFilters();
// If the filter list was modified, set bFilterUpdateRequested to suppress excessive re-filters between tree update
bFilterUpdateRequested = bFiltersUpdated;
return bFiltersUpdated;
}
return false;
}
void FSequencerNodeTree::Update()
{
using namespace UE::Sequencer;
FViewModelHierarchyOperation UpdateOp(RootNode->GetSharedData());
if (!ensure(RootNode))
{
return;
}
TSharedPtr<FSequenceModel> SequenceModel = RootNode->CastThisShared<FSequenceModel>();
check(SequenceModel);
UMovieSceneSequence* CurrentSequence = SequenceModel->GetSequence();
check(CurrentSequence);
UMovieScene* MovieScene = CurrentSequence->GetMovieScene();
CleanupMuteSolo(MovieScene);
// Cache pinned state of nodes, this needs to happen before UpdateFilters() below as some filters will look at the pinned state of child nodes
FPinnableExtensionShim::UpdateCachedPinnedState(RootNode);
// Re-filter the tree after updating
// @todo sequencer: Newly added sections may need to be visible even when there is a filter
bFilterUpdateRequested = true;
UpdateFilters();
// Sort all nodes
const bool bIncludeRootNode = true;
for (TSharedPtr<ISortableExtension> SortableChild : RootNode->GetDescendantsOfType<ISortableExtension>(bIncludeRootNode))
{
SortableChild->SortChildren();
}
// Avoid updating geometry during an undo/redo, as we may have changed the nodes and they won't get updated until next frame.
// Any deleted nodes will be present in the hierarchy but garbage.
if (!GIsTransacting)
{
// Update all virtual geometries
// This must happen after the sorting
IGeometryExtension::UpdateVirtualGeometry(0.f, RootNode);
}
// Update curve editor tree based on new filtered hierarchy
auto CurveEditorIntegration = SequenceModel->CastDynamic<FCurveEditorIntegrationExtension>();
if (CurveEditorIntegration)
{
CurveEditorIntegration->UpdateCurveEditor();
}
OnUpdatedDelegate.Broadcast();
}
UE::Sequencer::TViewModelPtr<UE::Sequencer::IOutlinerExtension> FindNodeWithPath(UE::Sequencer::TViewModelPtr<UE::Sequencer::IOutlinerExtension> InNode, const FString& NodePath)
{
using namespace UE::Sequencer;
if (!InNode)
{
return nullptr;
}
FString HeadPath, TailPath;
const bool bHasDelimiter = NodePath.Split(".", &HeadPath, &TailPath);
const FString NodeIdentifier = InNode->GetIdentifier().ToString();
if (bHasDelimiter)
{
if (NodeIdentifier != HeadPath)
{
// The node we're looking for is not in this sub-branch.
return nullptr;
}
}
else
{
// NodePath is just a name, so simply check with our node's name.
return (NodeIdentifier == NodePath) ? InNode : nullptr;
}
check(bHasDelimiter && !TailPath.IsEmpty());
for (TViewModelPtr<IOutlinerExtension> Child : InNode.AsModel()->GetChildrenOfType<IOutlinerExtension>())
{
TViewModelPtr<IOutlinerExtension> FoundNode = FindNodeWithPath(Child, TailPath);
if (FoundNode)
{
return FoundNode;
}
}
return nullptr;
}
UE::Sequencer::TViewModelPtr<UE::Sequencer::IOutlinerExtension> FSequencerNodeTree::GetNodeAtPath(const FString& NodePath) const
{
using namespace UE::Sequencer;
for (const TViewModelPtr<IOutlinerExtension>& RootChild : RootNode->GetChildrenOfType<IOutlinerExtension>())
{
TViewModelPtr<IOutlinerExtension> NodeAtPath = FindNodeWithPath(RootChild, NodePath);
if (NodeAtPath)
{
return NodeAtPath;
}
}
return nullptr;
}
void FSequencerNodeTree::SetRootNode(const UE::Sequencer::FViewModelPtr& InRootNode)
{
ensureMsgf(!RootNode, TEXT("Re-assinging the root node is currently an undefined behavior"));
RootNode = InRootNode;
}
UE::Sequencer::FViewModelPtr FSequencerNodeTree::GetRootNode() const
{
return RootNode;
}
TArray<UE::Sequencer::TViewModelPtr<UE::Sequencer::IOutlinerExtension>> FSequencerNodeTree::GetRootNodes() const
{
using namespace UE::Sequencer;
TArray<TViewModelPtr<IOutlinerExtension>> RootNodes;
for (const TViewModelPtr<IOutlinerExtension>& Child : RootNode->GetChildrenOfType<IOutlinerExtension>())
{
RootNodes.Add(Child);
}
return RootNodes;
}
void FSequencerNodeTree::ClearCustomSortOrders()
{
using namespace UE::Sequencer;
const bool bIncludeRootNode = true;
for (TSharedPtr<FViewModel> Child : RootNode->GetDescendants(bIncludeRootNode))
{
if (ISortableExtension* SortableExtension = Child->CastThis<ISortableExtension>())
{
SortableExtension->SetCustomOrder(-1);
}
}
}
void FSequencerNodeTree::SortAllNodesAndDescendants()
{
using namespace UE::Sequencer;
const bool bIncludeRootNode = true;
TArray<ISortableExtension*> SortableChildren;
for (TSharedPtr<FViewModel> Child : RootNode->GetDescendants(bIncludeRootNode))
{
if (ISortableExtension* SortableExtension = Child->CastThis<ISortableExtension>())
{
SortableChildren.Add(SortableExtension);
}
}
for (ISortableExtension* SortableChild : SortableChildren)
{
SortableChild->SortChildren();
}
}
void FSequencerNodeTree::SaveExpansionState(const UE::Sequencer::FViewModel& Node, bool bExpanded)
{
using namespace UE::Sequencer;
// @todo Sequencer - This should be moved to the sequence level
UMovieScene* MovieScene = Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene();
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
EditorData.ExpansionStates.Add(IOutlinerExtension::GetPathName(Node), FMovieSceneExpansionState(bExpanded));
}
TOptional<bool> FSequencerNodeTree::GetSavedExpansionState( const FViewModel& Node )
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene();
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
FMovieSceneExpansionState* ExpansionState = EditorData.ExpansionStates.Find(IOutlinerExtension::GetPathName(Node));
return ExpansionState ? ExpansionState->bExpanded : TOptional<bool>();
}
void FSequencerNodeTree::SavePinnedState(const UE::Sequencer::FViewModel& Node, bool bPinned)
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene();
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
if (bPinned)
{
EditorData.PinnedNodes.AddUnique(IOutlinerExtension::GetPathName(Node));
}
else
{
EditorData.PinnedNodes.RemoveSingle(IOutlinerExtension::GetPathName(Node));
}
}
bool FSequencerNodeTree::GetSavedPinnedState(const UE::Sequencer::FViewModel& Node) const
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene();
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
bool bPinned = EditorData.PinnedNodes.Contains(IOutlinerExtension::GetPathName(Node));
return bPinned;
}
bool FSequencerNodeTree::IsNodeFiltered(const TSharedPtr<UE::Sequencer::FViewModel>& Node) const
{
using namespace UE::Sequencer;
const TViewModelPtr<IOutlinerExtension> OutlinerItem = CastViewModel<IOutlinerExtension>(Node);
return OutlinerItem && !OutlinerItem->IsFilteredOut();
}
TSharedPtr<UE::Sequencer::FSectionModel> FSequencerNodeTree::GetSectionModel(const UMovieSceneSection* Section) const
{
using namespace UE::Sequencer;
FSectionModelStorageExtension* SectionStorage = RootNode->CastThis<FSectionModelStorageExtension>();
if (ensure(SectionStorage))
{
return SectionStorage->FindModelForSection(Section);
}
return nullptr;
}
bool FSequencerNodeTree::UpdateFilters()
{
using namespace UE::Sequencer;
if (!bFilterUpdateRequested)
{
return false;
}
const TSharedPtr<FSequencerFilterBar> FilterBar = Sequencer.GetFilterBar();
if (!FilterBar.IsValid())
{
return false;
}
const FSequencerFilterData& PreviousFilterData = FilterBar->GetFilterData();
const FSequencerFilterData& FilterData = FilterBar->FilterNodes();
bFilteringOnNodeGroups = Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetNodeGroups().HasAnyActiveFilter();
bFilterUpdateRequested = false;
// Return whether the new list of FilteredNodes is different than the previous list
return (PreviousFilterData.GetDisplayNodeCount() != FilterData.GetDisplayNodeCount()
|| PreviousFilterData != FilterData);
}
void FSequencerNodeTree::CleanupMuteSolo(UMovieScene* MovieScene)
{
// Remove mute/solo markers for any nodes that no longer exist
if (!MovieScene->IsReadOnly())
{
for (auto It = MovieScene->GetSoloNodes().CreateIterator(); It; ++It)
{
if (!GetNodeAtPath(*It))
{
It.RemoveCurrent();
}
}
for (auto It = MovieScene->GetMuteNodes().CreateIterator(); It; ++It)
{
if (!GetNodeAtPath(*It))
{
It.RemoveCurrent();
}
}
}
}
int32 FSequencerNodeTree::GetTotalDisplayNodeCount() const
{
return Sequencer.GetFilterBar()->GetFilterData().GetTotalNodeCount();
}
int32 FSequencerNodeTree::GetFilteredDisplayNodeCount() const
{
return Sequencer.GetFilterBar()->GetFilterData().GetDisplayNodeCount();
}
void FSequencerNodeTree::SetTextFilterString(const FString& InFilter)
{
const TSharedRef<FSequencerFilterBar> FilterBar = Sequencer.GetFilterBar();
const FString FilterString = FilterBar->GetTextFilterString();
if (InFilter != FilterString)
{
bFilterUpdateRequested = true;
FilterBar->SetTextFilterString(InFilter);
}
}
void FSequencerNodeTree::NodeGroupsCollectionChanged()
{
if (Sequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetNodeGroups().HasAnyActiveFilter() || bFilteringOnNodeGroups)
{
RequestFilterUpdate();
}
}
void FSequencerNodeTree::GetAllNodes(TArray<TSharedPtr<UE::Sequencer::FViewModel>>& OutNodes) const
{
using namespace UE::Sequencer;
const bool bIncludeRootNode = false;
for (const FViewModelPtr& It : RootNode->GetDescendants(bIncludeRootNode))
{
OutNodes.Add(It.AsModel());
}
}
void FSequencerNodeTree::GetAllNodes(TArray<TSharedRef<UE::Sequencer::FViewModel>>& OutNodes) const
{
using namespace UE::Sequencer;
const bool bIncludeRootNode = false;
for (const FViewModelPtr& It : RootNode->GetDescendants(bIncludeRootNode))
{
OutNodes.Add(It.AsModel().ToSharedRef());
}
}