2014-03-14 14:13:41 -04:00
|
|
|
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "SequencerPrivatePCH.h"
|
|
|
|
|
#include "MovieSceneSection.h"
|
|
|
|
|
#include "SequencerNodeTree.h"
|
2014-05-22 16:10:01 -04:00
|
|
|
#include "Sequencer.h"
|
2014-03-14 14:13:41 -04:00
|
|
|
#include "ScopedTransaction.h"
|
|
|
|
|
#include "MovieScene.h"
|
|
|
|
|
#include "MovieSceneTrack.h"
|
|
|
|
|
#include "MovieSceneTrackEditor.h"
|
|
|
|
|
#include "SectionLayoutBuilder.h"
|
|
|
|
|
#include "ISequencerSection.h"
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::Empty()
|
|
|
|
|
{
|
|
|
|
|
RootNodes.Empty();
|
|
|
|
|
ObjectBindingMap.Empty();
|
|
|
|
|
SelectedNodes.Empty();
|
|
|
|
|
EditorMap.Empty();
|
|
|
|
|
FilteredNodes.Empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::Update()
|
|
|
|
|
{
|
|
|
|
|
// @todo Sequencer - This update pass is too aggressive. Some nodes may still be valid
|
|
|
|
|
Empty();
|
|
|
|
|
|
|
|
|
|
UMovieScene* MovieScene = Sequencer.GetFocusedMovieScene();
|
|
|
|
|
|
|
|
|
|
// Get the master tracks so we can get sections from them
|
|
|
|
|
const TArray<UMovieSceneTrack*>& MasterTracks = MovieScene->GetMasterTracks();
|
|
|
|
|
|
|
|
|
|
for( int32 TrackIndex = 0; TrackIndex < MasterTracks.Num(); ++TrackIndex )
|
|
|
|
|
{
|
|
|
|
|
UMovieSceneTrack& Track = *MasterTracks[TrackIndex];
|
|
|
|
|
|
|
|
|
|
TSharedRef<FTrackNode> SectionNode = MakeShareable( new FTrackNode( Track.GetTrackName(), Track, NULL, *this ) );
|
|
|
|
|
RootNodes.Add( SectionNode );
|
|
|
|
|
|
|
|
|
|
MakeSectionInterfaces( Track, SectionNode );
|
|
|
|
|
|
|
|
|
|
SectionNode->PinNode();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TArray<FMovieSceneObjectBinding>& ObjectBindings = MovieScene->GetObjectBindings();
|
|
|
|
|
|
|
|
|
|
// Make nodes for all object bindings
|
|
|
|
|
for( int32 BindingIndex = 0; BindingIndex < ObjectBindings.Num(); ++BindingIndex )
|
|
|
|
|
{
|
|
|
|
|
TSharedRef<FObjectBindingNode> ObjectBindingNode = AddObjectBinding( ObjectBindings[BindingIndex].GetBindingName(), ObjectBindings[BindingIndex].GetObjectGuid() );
|
|
|
|
|
|
|
|
|
|
const TArray<UMovieSceneTrack*>& Tracks = ObjectBindings[BindingIndex].GetTracks();
|
|
|
|
|
|
|
|
|
|
for( int32 TrackIndex = 0; TrackIndex < Tracks.Num(); ++TrackIndex )
|
|
|
|
|
{
|
|
|
|
|
UMovieSceneTrack& Track = *Tracks[TrackIndex];
|
|
|
|
|
|
|
|
|
|
FName SectionName = Track.GetTrackName();
|
|
|
|
|
check( SectionName != NAME_None );
|
|
|
|
|
|
|
|
|
|
TSharedRef<FTrackNode> SectionAreaNode = ObjectBindingNode->AddSectionAreaNode( SectionName, Track );
|
|
|
|
|
MakeSectionInterfaces( Track, SectionAreaNode );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FRootNodeSorter
|
|
|
|
|
{
|
|
|
|
|
bool operator()( const TSharedRef<FSequencerDisplayNode>& A, const TSharedRef<FSequencerDisplayNode>& B ) const
|
|
|
|
|
{
|
|
|
|
|
return A->GetType() == ESequencerNode::Object ? false : true;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Sort so that master tracks appear before object tracks
|
|
|
|
|
RootNodes.Sort( FRootNodeSorter() );
|
|
|
|
|
|
|
|
|
|
// Re-filter the tree after updating
|
|
|
|
|
// @todo Sequencer - Newly added sections may need to be visible even when there is a filter
|
|
|
|
|
FilterNodes( FilterString );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TSharedRef<FMovieSceneTrackEditor> FSequencerNodeTree::FindOrAddTypeEditor( UMovieSceneTrack& InTrack )
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr<FMovieSceneTrackEditor> Editor = EditorMap.FindRef( &InTrack );
|
|
|
|
|
|
|
|
|
|
if( !Editor.IsValid() )
|
|
|
|
|
{
|
|
|
|
|
const TArray< TSharedPtr<FMovieSceneTrackEditor> >& TrackEditors = Sequencer.GetTrackEditors();
|
|
|
|
|
|
|
|
|
|
// Get a tool for each track
|
|
|
|
|
// @todo Sequencer - Should probably only need to get this once and it shouldnt be done here. It depends on when movie scene tool modules are loaded
|
|
|
|
|
TSharedPtr<FMovieSceneTrackEditor> SupportedTool;
|
|
|
|
|
for( int32 EditorIndex = 0; EditorIndex < TrackEditors.Num(); ++EditorIndex )
|
|
|
|
|
{
|
|
|
|
|
if( TrackEditors[EditorIndex]->SupportsType( InTrack.GetClass() ) )
|
|
|
|
|
{
|
|
|
|
|
Editor = TrackEditors[EditorIndex];
|
|
|
|
|
EditorMap.Add( &InTrack, Editor );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Editor.ToSharedRef();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::MakeSectionInterfaces( UMovieSceneTrack& Track, TSharedRef<FTrackNode>& SectionAreaNode )
|
|
|
|
|
{
|
|
|
|
|
const TArray<UMovieSceneSection*>& MovieSceneSections = Track.GetAllSections();
|
|
|
|
|
|
|
|
|
|
TSharedRef<FMovieSceneTrackEditor> Editor = FindOrAddTypeEditor( Track );
|
|
|
|
|
|
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < MovieSceneSections.Num(); ++SectionIndex )
|
|
|
|
|
{
|
|
|
|
|
UMovieSceneSection* SectionObject = MovieSceneSections[SectionIndex];
|
|
|
|
|
TSharedRef<ISequencerSection> Section = Editor->MakeSectionInterface( *SectionObject, &Track );
|
|
|
|
|
|
|
|
|
|
// Ask the section to generate it's inner layout
|
|
|
|
|
FSectionLayoutBuilder Builder( SectionAreaNode );
|
|
|
|
|
Section->GenerateSectionLayout( Builder );
|
|
|
|
|
|
|
|
|
|
SectionAreaNode->AddSection( Section );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SectionAreaNode->FixRowIndices();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TArray< TSharedRef<FSequencerDisplayNode> >& FSequencerNodeTree::GetRootNodes() const
|
|
|
|
|
{
|
|
|
|
|
return RootNodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TSharedRef<FObjectBindingNode> FSequencerNodeTree::AddObjectBinding( const FString& ObjectName, const FGuid& ObjectBinding )
|
|
|
|
|
{
|
|
|
|
|
// The node name is the object guid
|
|
|
|
|
FName ObjectNodeName = *ObjectBinding.ToString();
|
|
|
|
|
TSharedPtr<FSequencerDisplayNode> ParentNode = NULL;
|
|
|
|
|
|
|
|
|
|
TSharedRef< FObjectBindingNode > ObjectNode = MakeShareable( new FObjectBindingNode( ObjectNodeName, ObjectName, ObjectBinding, ParentNode, *this ) );
|
|
|
|
|
|
|
|
|
|
// Object binding nodes are always root nodes
|
|
|
|
|
RootNodes.Add( ObjectNode );
|
|
|
|
|
|
|
|
|
|
// Map the guid to the object binding node for fast lookup later
|
|
|
|
|
ObjectBindingMap.Add( ObjectBinding, ObjectNode );
|
|
|
|
|
|
|
|
|
|
return ObjectNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::SaveExpansionState( const FSequencerDisplayNode& Node, bool bExpanded )
|
|
|
|
|
{
|
|
|
|
|
UMovieScene* MovieScene = Sequencer.GetFocusedMovieScene();
|
|
|
|
|
|
|
|
|
|
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
|
|
|
|
|
|
|
|
|
|
if( bExpanded )
|
|
|
|
|
{
|
|
|
|
|
EditorData.CollapsedSequencerNodes.Remove( Node.GetPathName() );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Collapsed nodes are stored instead of expanded so that new nodes are expanded by default
|
|
|
|
|
EditorData.CollapsedSequencerNodes.AddUnique( Node.GetPathName() ) ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FSequencerNodeTree::GetSavedExpansionState( const FSequencerDisplayNode& Node ) const
|
|
|
|
|
{
|
|
|
|
|
UMovieScene* MovieScene = Sequencer.GetFocusedMovieScene();
|
|
|
|
|
|
|
|
|
|
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
|
|
|
|
|
|
|
|
|
|
// Collapsed nodes are stored instead of expanded so that new nodes are expanded by default
|
|
|
|
|
bool bCollapsed = EditorData.CollapsedSequencerNodes.Contains( Node.GetPathName() );
|
|
|
|
|
|
|
|
|
|
return !bCollapsed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::SetSelectionState( TSharedRef<const FSequencerDisplayNode> AffectedNode, bool bSelect, bool bDeselectOtherNodes )
|
|
|
|
|
{
|
|
|
|
|
if( bSelect && AffectedNode->IsSelectable() )
|
|
|
|
|
{
|
|
|
|
|
if( bDeselectOtherNodes )
|
|
|
|
|
{
|
|
|
|
|
// empty current selection set unless multiple selecting
|
|
|
|
|
SelectedNodes.Empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SelectedNodes.Add( AffectedNode );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Not selecting so remove the node from the selection set
|
|
|
|
|
SelectedNodes.Remove( AffectedNode );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FSequencerNodeTree::IsNodeSelected( TSharedRef<const FSequencerDisplayNode> Node ) const
|
|
|
|
|
{
|
|
|
|
|
return SelectedNodes.Contains( Node );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FSequencerNodeTree::IsNodeFiltered( const TSharedRef<const FSequencerDisplayNode> Node ) const
|
|
|
|
|
{
|
|
|
|
|
return FilteredNodes.Contains( Node );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recursively filters nodes
|
|
|
|
|
*
|
|
|
|
|
* @param StartNode The node to start from
|
|
|
|
|
* @param FilterStrings The filter strings which need to be matched
|
|
|
|
|
* @param OutFilteredNodes The list of all filtered nodes
|
|
|
|
|
*/
|
|
|
|
|
static bool FilterNodesRecursive( const TSharedRef<FSequencerDisplayNode>& StartNode, const TArray<FString>& FilterStrings, TSet< TSharedRef< const FSequencerDisplayNode> >& OutFilteredNodes )
|
|
|
|
|
{
|
|
|
|
|
// Assume the filter is acceptable
|
|
|
|
|
bool bFilterAcceptable = true;
|
|
|
|
|
|
|
|
|
|
// Check each string in the filter strings list against
|
|
|
|
|
for (int32 TestNameIndex = 0; TestNameIndex < FilterStrings.Num(); ++TestNameIndex)
|
|
|
|
|
{
|
|
|
|
|
const FString& TestName = FilterStrings[TestNameIndex];
|
|
|
|
|
|
|
|
|
|
if ( !StartNode->GetDisplayName().Contains( TestName ) )
|
|
|
|
|
{
|
|
|
|
|
bFilterAcceptable = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Whether or the start node is in the filter
|
|
|
|
|
bool bInFilter = false;
|
|
|
|
|
|
|
|
|
|
if( bFilterAcceptable )
|
|
|
|
|
{
|
|
|
|
|
// This node is now filtered
|
|
|
|
|
OutFilteredNodes.Add( StartNode );
|
|
|
|
|
bInFilter = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check each child node to determine if it is filtered
|
|
|
|
|
const TArray< TSharedRef<FSequencerDisplayNode> >& ChildNodes = StartNode->GetChildNodes();
|
|
|
|
|
for( int32 ChildIndex = 0; ChildIndex < ChildNodes.Num(); ++ChildIndex )
|
|
|
|
|
{
|
|
|
|
|
// Mark the parent as filtered if any child node was filtered
|
|
|
|
|
bFilterAcceptable |= FilterNodesRecursive( ChildNodes[ChildIndex], FilterStrings, OutFilteredNodes );
|
|
|
|
|
if( bFilterAcceptable && !bInFilter )
|
|
|
|
|
{
|
|
|
|
|
OutFilteredNodes.Add( StartNode );
|
|
|
|
|
bInFilter = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bFilterAcceptable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::FilterNodes( const FString& InFilter )
|
|
|
|
|
{
|
|
|
|
|
FilteredNodes.Empty();
|
|
|
|
|
|
|
|
|
|
if( InFilter.IsEmpty() )
|
|
|
|
|
{
|
|
|
|
|
// No filter
|
|
|
|
|
FilterString.Empty();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Build a list of strings that must be matched
|
|
|
|
|
TArray<FString> FilterStrings;
|
|
|
|
|
|
|
|
|
|
FilterString = InFilter;
|
|
|
|
|
// Remove whitespace from the front and back of the string
|
|
|
|
|
FilterString.Trim();
|
|
|
|
|
FilterString.TrimTrailing();
|
|
|
|
|
const bool bCullEmpty = true;
|
|
|
|
|
FilterString.ParseIntoArray( &FilterStrings, TEXT(" "), bCullEmpty );
|
|
|
|
|
|
|
|
|
|
for( auto It = ObjectBindingMap.CreateIterator(); It; ++It )
|
|
|
|
|
{
|
|
|
|
|
// Recursively filter all nodes, matching them against the list of filter strings. All filter strings must be matched
|
|
|
|
|
FilterNodesRecursive( It.Value().ToSharedRef(), FilterStrings, FilteredNodes );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FSequencerNodeTree::UpdateCachedVisibilityBasedOnShotFiltersChanged()
|
|
|
|
|
{
|
|
|
|
|
for (int32 i = 0; i < RootNodes.Num(); ++i)
|
|
|
|
|
{
|
|
|
|
|
RootNodes[i]->UpdateCachedShotFilteredVisibility();
|
|
|
|
|
}
|
|
|
|
|
}
|