Files
UnrealEngineUWP/Engine/Source/Editor/Sequencer/Private/DisplayNodes/SequencerDisplayNode.cpp
Max Chen 4cde9c20a0 Copying //UE4/Dev-Sequencer to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2875025 on 2016/02/20 by Andrew.Rodham

	Sequencer: Cinematic viewport improvements
	  - Added optional letterbox overlay (defaults to 2.35:1)
	  - Added ability to change safe frame colors
	  - Added selected tracks' keys to the transport range
	  - Added buttons for jumping between selected tracks' keyframes on the transport controls
	  - Removed black padding around the viewport where possible
	  - Added ability to specify whether a combo button/menu anchor should close when its parent receives focus
	  - Separated logic of FGroupedKeyArea into FSequencerKeyCollection, so it can be used independently
	  - Added playback range to the viewport frame numbers
	  - All frame numbers are now spin boxes

	#jira UE-26429

Change 2875026 on 2016/02/20 by Thomas.Sarkanen

	Added console commands for recording sequences

	Changed plugin to a developer plugin so we can load it when the editor is in -game mode.
	Added Exec commands.
	Added some more logging to help diagnose problems when using commands.
	Added loading/saving of config for recorder settings (stored in Editor.ini).
	Also disabled controls in recorder window when recording.
	Added auto-saving of assets when in non-editor modes.
	Moved animation settings from UnrealEd to Engine module.

Change 2875036 on 2016/02/20 by Max.Chen

	Sequencer: Call RedrawAllViewports instead of RedrawLevelEditingViewports. In particular, this fixes some update issues when editing values in the key editors.

	#jira UE-26960

Change 2875046 on 2016/02/20 by Max.Preussner

	Sequencer: Fix so that clicking on UMG Animations doesn't dirty the scene.

	#jira UE-26249

Change 2875047 on 2016/02/20 by Max.Chen

	Sequencer: Add option to toggle display of channel colors/lines. View->Channel Colors

Change 2877138 on 2016/02/23 by Max.Chen

	Sequencer: Select corresponding track node when selecting key or section. Removed active/inactive selection since it was only being used in deletion and the rules for deletion are now dependent upon what is selected - delete keys and sections before deleting outliner nodes.

Change 2877143 on 2016/02/23 by Thomas.Sarkanen

	Added new math function: WindRelativeAnglesDegrees

	Given two angles in degrees, 'wind' the angle in Angle1 so that it avoids >180 degree flips.
	Good for winding rotations previously expressed as quaternions into a euler-angle representation.

Change 2877147 on 2016/02/23 by Thomas.Sarkanen

	Added the ability to import sequencer transforms from the root node of an animation sequence

	Intended for use after re-importing animations from DCC tools.
	Available in the right-click menu for transform tracks.
	Also added FindTrackBinding to UMovieScene so track bindings can be recovered from tracks.

Change 2877163 on 2016/02/23 by Max.Chen

	Sequencer: Add option to create keyframe sections as infinite. Sequencer defaults to true, UMG defaults to false.

Change 2877165 on 2016/02/23 by Max.Preussner

	Sequencer: Drawing vertical position lines when dragging keys

Change 2878748 on 2016/02/23 by Max.Chen

	Curve Editor: Switch curve type to user when flatting or straightening tangents.

	#jira UE-27277

Change 2878799 on 2016/02/23 by Frank.Fella

	Sequencer - Add folders support to the outliner.

Change 2880769 on 2016/02/24 by Andrew.Rodham

	Sequencer: Added ability to override runtime spawnable ownership in sequencer
	  - This is exposed as an option on spawnables "Keep Alive Outside Playback Range (In Sequencer)"
	  - Enabling this will stop spawnables from being destroyed when scrubbing outside of the playback range

	#jira UE-27205

Change 2880770 on 2016/02/24 by Thomas.Sarkanen

	Sequencer: Added countdown and recording indicator display when recording

	Also fixed extra popups added post-PIE when animation recordings auto shutdown.

Change 2880782 on 2016/02/24 by Max.Chen

	Sequencer: Snapping now also uses the current time as a possible snap time.

	#jira UE-26306

Change 2880793 on 2016/02/24 by Max.Chen

	Sequencer: Add +Animation to Animation track so that it's consistent with all other tracks that have a + button.

Change 2880812 on 2016/02/24 by Max.Chen

	Sequencer: Fix adjusting the leading edge of a shot section so that it cuts into the shot rather than adjusts the start time.

	#jira UE-26306

Change 2881624 on 2016/02/25 by Andrew.Rodham

	Changing shader version GUID to fix corrupt shaders in ddc

Change 2882408 on 2016/02/25 by Thomas.Sarkanen

	Asset/actors stored in TLazyObjectPtrs can now reference game content from engine

	This is a legitimate use case as lazy object ptrs are designed to reference assets/actors cross-domain.

Change 2882409 on 2016/02/25 by Thomas.Sarkanen

[CL 2899785 by Max Chen in Main branch]
2016-03-08 16:55:04 -05:00

614 lines
15 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "SequencerPrivatePCH.h"
#include "SequencerSectionLayoutBuilder.h"
#include "ISequencerSection.h"
#include "Sequencer.h"
#include "MovieScene.h"
#include "SSequencer.h"
#include "SSequencerSectionAreaView.h"
#include "MovieSceneSection.h"
#include "MovieSceneSequence.h"
#include "MovieSceneTrack.h"
#include "MovieSceneTrackEditor.h"
#include "CommonMovieSceneTools.h"
#include "IKeyArea.h"
#include "GroupedKeyArea.h"
#include "ObjectEditorUtils.h"
#define LOCTEXT_NAMESPACE "SequencerDisplayNode"
/** When 0, regeneration of dynamic key groups is enabled, when non-zero, such behaviour is disabled */
FThreadSafeCounter KeyGroupRegenerationLock;
namespace SequencerNodeConstants
{
extern const float CommonPadding;
const float CommonPadding = 4.f;
}
class SSequencerObjectTrack
: public SLeafWidget
{
public:
SLATE_BEGIN_ARGS(SSequencerObjectTrack) {}
/** The view range of the section area */
SLATE_ATTRIBUTE( TRange<float>, ViewRange )
SLATE_END_ARGS()
/** SLeafWidget Interface */
virtual int32 OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const override;
virtual FVector2D ComputeDesiredSize(float) const override;
void Construct( const FArguments& InArgs, TSharedRef<FSequencerDisplayNode> InRootNode )
{
RootNode = InRootNode;
ViewRange = InArgs._ViewRange;
check(RootNode->GetType() == ESequencerNode::Object);
}
private:
/** Collects all key times from the root node */
void CollectAllKeyTimes(TArray<float>& OutKeyTimes) const;
/** Adds a key time uniquely to an array of key times */
void AddKeyTime(const float& NewTime, TArray<float>& OutKeyTimes) const;
private:
/** Root node of this track view panel */
TSharedPtr<FSequencerDisplayNode> RootNode;
/** The current view range */
TAttribute< TRange<float> > ViewRange;
};
int32 SSequencerObjectTrack::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
if (RootNode->GetSequencer().GetSettings()->GetShowCombinedKeyframes())
{
TArray<float> OutKeyTimes;
CollectAllKeyTimes(OutKeyTimes);
FTimeToPixel TimeToPixelConverter(AllottedGeometry, ViewRange.Get());
for (int32 i = 0; i < OutKeyTimes.Num(); ++i)
{
float KeyPosition = TimeToPixelConverter.TimeToPixel(OutKeyTimes[i]);
static const FVector2D KeyMarkSize = FVector2D(3.f, 21.f);
FSlateDrawElement::MakeBox(
OutDrawElements,
LayerId+1,
AllottedGeometry.ToPaintGeometry(FVector2D(KeyPosition - FMath::CeilToFloat(KeyMarkSize.X/2.f), FMath::CeilToFloat(AllottedGeometry.Size.Y/2.f - KeyMarkSize.Y/2.f)), KeyMarkSize),
FEditorStyle::GetBrush("Sequencer.KeyMark"),
MyClippingRect,
ESlateDrawEffect::None,
FLinearColor(1.f, 1.f, 1.f, 1.f)
);
}
return LayerId+1;
}
return LayerId;
}
FVector2D SSequencerObjectTrack::ComputeDesiredSize( float ) const
{
// Note: X Size is not used
return FVector2D( 100.0f, RootNode->GetNodeHeight() );
}
void SSequencerObjectTrack::CollectAllKeyTimes(TArray<float>& OutKeyTimes) const
{
TArray<TSharedRef<FSequencerSectionKeyAreaNode>> OutNodes;
RootNode->GetChildKeyAreaNodesRecursively(OutNodes);
for (int32 i = 0; i < OutNodes.Num(); ++i)
{
TArray< TSharedRef<IKeyArea> > KeyAreas = OutNodes[i]->GetAllKeyAreas();
for (int32 j = 0; j < KeyAreas.Num(); ++j)
{
TArray<FKeyHandle> KeyHandles = KeyAreas[j]->GetUnsortedKeyHandles();
for (int32 k = 0; k < KeyHandles.Num(); ++k)
{
AddKeyTime(KeyAreas[j]->GetKeyTime(KeyHandles[k]), OutKeyTimes);
}
}
}
}
void SSequencerObjectTrack::AddKeyTime(const float& NewTime, TArray<float>& OutKeyTimes) const
{
// @todo Sequencer It might be more efficient to add each key and do the pruning at the end
for (float& KeyTime : OutKeyTimes)
{
if (FMath::IsNearlyEqual(KeyTime, NewTime))
{
return;
}
}
OutKeyTimes.Add(NewTime);
}
FSequencerDisplayNode::FSequencerDisplayNode( FName InNodeName, TSharedPtr<FSequencerDisplayNode> InParentNode, FSequencerNodeTree& InParentTree )
: VirtualTop( 0.f )
, VirtualBottom( 0.f )
, ParentNode( InParentNode )
, ParentTree( InParentTree )
, NodeName( InNodeName )
, bExpanded( false )
{
}
void FSequencerDisplayNode::Initialize(float InVirtualTop, float InVirtualBottom)
{
bExpanded = ParentTree.GetSavedExpansionState( *this );
VirtualTop = InVirtualTop;
VirtualBottom = InVirtualBottom;
}
void FSequencerDisplayNode::AddObjectBindingNode(TSharedRef<FSequencerObjectBindingNode> ObjectBindingNode)
{
AddChildAndSetParent( ObjectBindingNode );
}
bool FSequencerDisplayNode::Traverse_ChildFirst(const TFunctionRef<bool(FSequencerDisplayNode&)>& InPredicate, bool bIncludeThisNode)
{
for (auto& Child : GetChildNodes())
{
if (!Child->Traverse_ChildFirst(InPredicate, true))
{
return false;
}
}
return bIncludeThisNode ? InPredicate(*this) : true;
}
bool FSequencerDisplayNode::Traverse_ParentFirst(const TFunctionRef<bool(FSequencerDisplayNode&)>& InPredicate, bool bIncludeThisNode)
{
if (bIncludeThisNode && !InPredicate(*this))
{
return false;
}
for (auto& Child : GetChildNodes())
{
if (!Child->Traverse_ParentFirst(InPredicate, true))
{
return false;
}
}
return true;
}
bool FSequencerDisplayNode::TraverseVisible_ChildFirst(const TFunctionRef<bool(FSequencerDisplayNode&)>& InPredicate, bool bIncludeThisNode)
{
// If the item is not expanded, its children ain't visible
if (IsExpanded())
{
for (auto& Child : GetChildNodes())
{
if (!Child->IsHidden() && !Child->TraverseVisible_ChildFirst(InPredicate, true))
{
return false;
}
}
}
if (bIncludeThisNode && !IsHidden())
{
return InPredicate(*this);
}
// Continue iterating regardless of visibility
return true;
}
bool FSequencerDisplayNode::TraverseVisible_ParentFirst(const TFunctionRef<bool(FSequencerDisplayNode&)>& InPredicate, bool bIncludeThisNode)
{
if (bIncludeThisNode && !IsHidden() && !InPredicate(*this))
{
return false;
}
// If the item is not expanded, its children ain't visible
if (IsExpanded())
{
for (auto& Child : GetChildNodes())
{
if (!Child->IsHidden() && !Child->TraverseVisible_ParentFirst(InPredicate, true))
{
return false;
}
}
}
return true;
}
TSharedRef<FSequencerSectionCategoryNode> FSequencerDisplayNode::AddCategoryNode( FName CategoryName, const FText& DisplayLabel )
{
TSharedPtr<FSequencerSectionCategoryNode> CategoryNode;
// See if there is an already existing category node to use
for (const TSharedRef<FSequencerDisplayNode>& Node : ChildNodes)
{
if ((Node->GetNodeName() == CategoryName) && (Node->GetType() == ESequencerNode::Category))
{
CategoryNode = StaticCastSharedRef<FSequencerSectionCategoryNode>(Node);
}
}
if (!CategoryNode.IsValid())
{
// No existing category found, make a new one
CategoryNode = MakeShareable(new FSequencerSectionCategoryNode(CategoryName, DisplayLabel, SharedThis(this), ParentTree));
ChildNodes.Add(CategoryNode.ToSharedRef());
}
return CategoryNode.ToSharedRef();
}
TSharedRef<FSequencerTrackNode> FSequencerDisplayNode::AddSectionAreaNode(UMovieSceneTrack& AssociatedTrack, ISequencerTrackEditor& AssociatedEditor)
{
TSharedPtr<FSequencerTrackNode> SectionNode;
// see if there is an already existing section node to use
for (const TSharedRef<FSequencerDisplayNode>& Node : ChildNodes)
{
if (Node->GetType() != ESequencerNode::Track)
{
continue;
}
SectionNode = StaticCastSharedRef<FSequencerTrackNode>(Node);
if (SectionNode->GetTrack() != &AssociatedTrack)
{
SectionNode.Reset();
}
else
{
break;
}
}
if (!SectionNode.IsValid())
{
// No existing node found make a new one
SectionNode = MakeShareable( new FSequencerTrackNode( AssociatedTrack, AssociatedEditor, false, SharedThis(this), ParentTree ) );
ChildNodes.Add( SectionNode.ToSharedRef() );
}
// The section node type has to match
check(SectionNode->GetTrack() == &AssociatedTrack);
return SectionNode.ToSharedRef();
}
void FSequencerDisplayNode::AddKeyAreaNode(FName KeyAreaName, const FText& DisplayName, TSharedRef<IKeyArea> KeyArea)
{
TSharedPtr<FSequencerSectionKeyAreaNode> KeyAreaNode;
// See if there is an already existing key area node to use
for (const TSharedRef<FSequencerDisplayNode>& Node : ChildNodes)
{
if ((Node->GetNodeName() == KeyAreaName) && (Node->GetType() == ESequencerNode::KeyArea))
{
KeyAreaNode = StaticCastSharedRef<FSequencerSectionKeyAreaNode>(Node);
}
}
if (!KeyAreaNode.IsValid())
{
// No existing node found make a new one
KeyAreaNode = MakeShareable( new FSequencerSectionKeyAreaNode( KeyAreaName, DisplayName, SharedThis( this ), ParentTree ) );
ChildNodes.Add(KeyAreaNode.ToSharedRef());
}
KeyAreaNode->AddKeyArea(KeyArea);
}
FLinearColor FSequencerDisplayNode::GetDisplayNameColor() const
{
return FLinearColor( 1.f, 1.f, 1.f, 1.f );
}
FText FSequencerDisplayNode::GetDisplayNameToolTipText() const
{
return FText();
}
TSharedRef<SWidget> FSequencerDisplayNode::GenerateContainerWidgetForOutliner(const TSharedRef<SSequencerTreeViewRow>& InRow)
{
auto NewWidget = SNew(SAnimationOutlinerTreeNode, SharedThis(this), InRow)
.IconBrush(this, &FSequencerDisplayNode::GetIconBrush)
.IconOverlayBrush(this, &FSequencerDisplayNode::GetIconOverlayBrush)
.IconToolTipText(this, &FSequencerDisplayNode::GetIconToolTipText)
.CustomContent()
[
GetCustomOutlinerContent()
];
return NewWidget;
}
TSharedRef<SWidget> FSequencerDisplayNode::GetCustomOutlinerContent()
{
return SNew(SSpacer);
}
const FSlateBrush* FSequencerDisplayNode::GetIconBrush() const
{
return nullptr;
}
const FSlateBrush* FSequencerDisplayNode::GetIconOverlayBrush() const
{
return nullptr;
}
FText FSequencerDisplayNode::GetIconToolTipText() const
{
return FText();
}
TSharedRef<SWidget> FSequencerDisplayNode::GenerateWidgetForSectionArea(const TAttribute< TRange<float> >& ViewRange)
{
if (GetType() == ESequencerNode::Track)
{
return SNew(SSequencerSectionAreaView, SharedThis(this))
.ViewRange(ViewRange);
}
if (GetType() == ESequencerNode::Object)
{
return SNew(SSequencerObjectTrack, SharedThis(this))
.ViewRange(ViewRange);
}
// currently only section areas display widgets
return SNullWidget::NullWidget;
}
TSharedPtr<FSequencerDisplayNode> FSequencerDisplayNode::GetSectionAreaAuthority() const
{
TSharedPtr<FSequencerDisplayNode> Authority = SharedThis(const_cast<FSequencerDisplayNode*>(this));
while (Authority.IsValid())
{
if (Authority->GetType() == ESequencerNode::Object || Authority->GetType() == ESequencerNode::Track)
{
return Authority;
}
else
{
Authority = Authority->GetParent();
}
}
return Authority;
}
FString FSequencerDisplayNode::GetPathName() const
{
// First get our parent's path
FString PathName;
if (ParentNode.IsValid())
{
PathName = ParentNode.Pin()->GetPathName() + TEXT(".");
}
//then append our path
PathName += GetNodeName().ToString();
return PathName;
}
TSharedPtr<SWidget> FSequencerDisplayNode::OnSummonContextMenu()
{
// @todo sequencer replace with UI Commands instead of faking it
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GetSequencer().GetCommandBindings());
BuildContextMenu(MenuBuilder);
return MenuBuilder.MakeWidget();
}
void FSequencerDisplayNode::BuildContextMenu(FMenuBuilder& MenuBuilder)
{
TSharedRef<FSequencerDisplayNode> ThisNode = SharedThis(this);
MenuBuilder.BeginSection("Edit", LOCTEXT("EditContextMenuSectionName", "Edit"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ToggleNodeActive", "Active"),
LOCTEXT("ToggleNodeActiveTooltip", "Set this track or selected tracks active/inactive"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(&GetSequencer(), &FSequencer::ToggleNodeActive),
FCanExecuteAction(),
FIsActionChecked::CreateSP(&GetSequencer(), &FSequencer::IsNodeActive)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("ToggleNodeLock", "Locked"),
LOCTEXT("ToggleNodeLockTooltip", "Lock or unlock this node or selected tracks"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(&GetSequencer(), &FSequencer::ToggleNodeLocked),
FCanExecuteAction(),
FIsActionChecked::CreateSP(&GetSequencer(), &FSequencer::IsNodeLocked)
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("DeleteNode", "Delete"),
LOCTEXT("DeleteNodeTooltip", "Delete this or selected tracks"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(&GetSequencer(), &FSequencer::DeleteNode, ThisNode))
);
MenuBuilder.AddMenuEntry(
LOCTEXT("RenameNode", "Rename"),
LOCTEXT("RenameNodeTooltip", "Rename this track"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateSP(this, &FSequencerDisplayNode::HandleContextMenuRenameNodeExecute),
FCanExecuteAction::CreateSP(this, &FSequencerDisplayNode::HandleContextMenuRenameNodeCanExecute)
)
);
}
MenuBuilder.EndSection();
}
void FSequencerDisplayNode::GetChildKeyAreaNodesRecursively(TArray< TSharedRef<FSequencerSectionKeyAreaNode> >& OutNodes) const
{
for (const TSharedRef<FSequencerDisplayNode>& Node : ChildNodes)
{
if (Node->GetType() == ESequencerNode::KeyArea)
{
OutNodes.Add(StaticCastSharedRef<FSequencerSectionKeyAreaNode>(Node));
}
Node->GetChildKeyAreaNodesRecursively(OutNodes);
}
}
void FSequencerDisplayNode::SetExpansionState(bool bInExpanded)
{
bExpanded = bInExpanded;
// Expansion state has changed, save it to the movie scene now
ParentTree.SaveExpansionState(*this, bExpanded);
}
bool FSequencerDisplayNode::IsExpanded() const
{
return bExpanded;
}
bool FSequencerDisplayNode::IsHidden() const
{
return ParentTree.HasActiveFilter() && !ParentTree.IsNodeFiltered(AsShared());
}
bool FSequencerDisplayNode::IsHovered() const
{
return ParentTree.GetHoveredNode().Get() == this;
}
TSharedRef<FGroupedKeyArea> FSequencerDisplayNode::GetKeyGrouping(int32 InSectionIndex)
{
if (!KeyGroupings.IsValidIndex(InSectionIndex))
{
KeyGroupings.SetNum(InSectionIndex + 1);
}
auto& KeyGroup = KeyGroupings[InSectionIndex];
if (!KeyGroup.IsValid())
{
KeyGroup = MakeShareable(new FGroupedKeyArea(*this, InSectionIndex));
}
return KeyGroup.ToSharedRef();
}
TSharedRef<FGroupedKeyArea> FSequencerDisplayNode::UpdateKeyGrouping(int32 InSectionIndex)
{
if (!KeyGroupings.IsValidIndex(InSectionIndex))
{
KeyGroupings.SetNum(InSectionIndex + 1);
}
auto& KeyGroup = KeyGroupings[InSectionIndex];
if (!KeyGroup.IsValid())
{
KeyGroup = MakeShareable(new FGroupedKeyArea(*this, InSectionIndex));
}
else if (KeyGroupRegenerationLock.GetValue() == 0)
{
*KeyGroup = FGroupedKeyArea(*this, InSectionIndex);
}
return KeyGroup.ToSharedRef();
}
void FSequencerDisplayNode::HandleContextMenuRenameNodeExecute()
{
RenameRequestedEvent.Broadcast();
}
bool FSequencerDisplayNode::HandleContextMenuRenameNodeCanExecute() const
{
return CanRenameNode();
}
void FSequencerDisplayNode::DisableKeyGoupingRegeneration()
{
KeyGroupRegenerationLock.Increment();
}
void FSequencerDisplayNode::EnableKeyGoupingRegeneration()
{
KeyGroupRegenerationLock.Decrement();
}
void FSequencerDisplayNode::AddChildAndSetParent( TSharedRef<FSequencerDisplayNode> InChild )
{
ChildNodes.Add( InChild );
InChild->ParentNode = SharedThis( this );
}
#undef LOCTEXT_NAMESPACE