Files
UnrealEngineUWP/Engine/Source/Editor/Sequencer/Private/SAnimationOutlinerTreeNode.cpp
Andrew Rodham 4451b46bb4 Miscellaneous sequencer improvements
- Sequencer tree is now an STreeView which gives us proper virtualized scroll, and allows removal of lots of manually implemented tree-management code.
  - Selection and expansion is now handled by STreeView itself.
  - Track lanes are now also virtualized vertically, so we should only see section area widgets when they're actually on screen. Positioning these elements is a frame behind the tree rows.
- Added Traversal methods for more expressive traversal of the sequencer tree (See FSequencerDisplayNode::Traverse(Visible)_(Child|Parent)First).
- Removed outliner backgrtounds and replaced with more subtle separator lines.
- Added Marquee selection for sequencer keys. Currently this is not implented when LMB dragging on a SSection.
- Added RMB panning for the sequencer track area. This currently has no inertial scroll, but could be added in future.

[CL 2625643 by Andrew Rodham in Main branch]
2015-07-20 05:34:00 -04:00

524 lines
15 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "SequencerPrivatePCH.h"
#include "SAnimationOutlinerTreeNode.h"
#include "ScopedTransaction.h"
#include "Sequencer.h"
#include "MovieScene.h"
#include "MovieSceneSection.h"
#include "MovieSceneCommonHelpers.h"
#include "Engine/Selection.h"
#include "IKeyArea.h"
#include "SSequencerTreeView.h"
#define LOCTEXT_NAMESPACE "AnimationOutliner"
void SAnimationOutlinerTreeNode::Construct( const FArguments& InArgs, TSharedRef<FSequencerDisplayNode> Node, const TSharedRef<SSequencerTreeViewRow>& InTableRow, FSequencer* InSequencer )
{
Sequencer = InSequencer;
DisplayNode = Node;
SelectedBrush = FEditorStyle::GetBrush( "Sequencer.AnimationOutliner.SelectionBorder" );
SelectedBrushInactive = FEditorStyle::GetBrush("Sequencer.AnimationOutliner.SelectionBorderInactive");
NotSelectedBrush = FEditorStyle::GetBrush( "NoBorder" );
TableRowStyle = &FEditorStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row");
// Choose the font. If the node is a root node or an object node, we show a larger font for it.
FSlateFontInfo NodeFont = Node->GetParent().IsValid() && Node->GetType() != ESequencerNode::Object ?
FEditorStyle::GetFontStyle("Sequencer.AnimationOutliner.RegularFont") :
FEditorStyle::GetFontStyle("Sequencer.AnimationOutliner.BoldFont");
TSharedRef<SWidget> TextWidget =
SNew( STextBlock )
.Text(this, &SAnimationOutlinerTreeNode::GetDisplayName )
.Font( NodeFont );
auto NodeHeight = [=]() -> FOptionalSize { return DisplayNode->GetNodeHeight(); };
TAttribute<FLinearColor> HoverTint(this, &SAnimationOutlinerTreeNode::GetHoverTint);
ForegroundColor.Bind(this, &SAnimationOutlinerTreeNode::GetForegroundBasedOnSelection);
TSharedRef<SWidget> FinalWidget =
SNew( SBorder )
.VAlign( VAlign_Center )
.BorderImage( this, &SAnimationOutlinerTreeNode::GetNodeBorderImage )
.Padding(FMargin(0, Node->GetNodePadding().Combined() / 2))
[
SNew(SBox)
.HeightOverride_Lambda(NodeHeight)
[
SNew( SHorizontalBox )
// Expand track lanes button
+ SHorizontalBox::Slot()
.Padding(FMargin(2.f, 0.f))
.VAlign( VAlign_Center )
.AutoWidth()
[
SNew(SExpanderArrow, InTableRow).IndentAmount(SequencerLayoutConstants::IndentAmount)
]
// Label Slot
+ SHorizontalBox::Slot()
.VAlign( VAlign_Center )
.FillWidth(3)
[
TextWidget
]
// Editor slot
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
// @todo Sequencer - Remove this box and width override.
SNew(SBox)
.WidthOverride(100)
[
DisplayNode->GenerateEditWidgetForOutliner()
]
]
// Previous key slot
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
.Padding(3, 0, 0, 0)
[
SNew(SBorder)
.Padding(0)
.BorderImage(NotSelectedBrush)
.ColorAndOpacity(HoverTint)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "FlatButton")
.ToolTipText(LOCTEXT("PreviousKeyButton", "Set the time to the previous key"))
.OnClicked(this, &SAnimationOutlinerTreeNode::OnPreviousKeyClicked)
.ForegroundColor( FSlateColor::UseForeground() )
.ContentPadding(0)
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.7"))
.Text(FText::FromString(FString(TEXT("\xf060"))) /*fa-arrow-left*/)
]
]
]
// Add key slot
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SBorder)
.Padding(0)
.BorderImage(NotSelectedBrush)
.ColorAndOpacity(HoverTint)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "FlatButton")
.ToolTipText(LOCTEXT("AddKeyButton", "Add a new key at the current time"))
.OnClicked(this, &SAnimationOutlinerTreeNode::OnAddKeyClicked)
.ForegroundColor( FSlateColor::UseForeground() )
.ContentPadding(0)
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.7"))
.Text(FText::FromString(FString(TEXT("\xf055"))) /*fa-plus-circle*/)
]
]
]
// Next key slot
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SBorder)
.Padding(0)
.BorderImage(NotSelectedBrush)
.ColorAndOpacity(HoverTint)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "FlatButton")
.ToolTipText(LOCTEXT("NextKeyButton", "Set the time to the next key"))
.OnClicked(this, &SAnimationOutlinerTreeNode::OnNextKeyClicked)
.ContentPadding(0)
.ForegroundColor( FSlateColor::UseForeground() )
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.7"))
.Text(FText::FromString(FString(TEXT("\xf061"))) /*fa-arrow-right*/)
]
]
]
]
];
ChildSlot
[
FinalWidget
];
}
FLinearColor SAnimationOutlinerTreeNode::GetHoverTint() const
{
return IsHovered() ? FLinearColor(1,1,1,0.9f) : FLinearColor(1,1,1,0.4f);
}
FReply SAnimationOutlinerTreeNode::OnPreviousKeyClicked()
{
FSequencer& Sequencer = DisplayNode->GetSequencer();
float ClosestPreviousKeyDistance = MAX_FLT;
float CurrentTime = Sequencer.GetCurrentLocalTime(*Sequencer.GetFocusedMovieScene());
float PreviousTime = 0;
bool PreviousKeyFound = false;
TSet<TSharedPtr<IKeyArea>> KeyAreas;
SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas);
for (TSharedPtr<IKeyArea> KeyArea : KeyAreas)
{
for (FKeyHandle& KeyHandle : KeyArea->GetUnsortedKeyHandles())
{
float KeyTime = KeyArea->GetKeyTime(KeyHandle);
if (KeyTime < CurrentTime && CurrentTime - KeyTime < ClosestPreviousKeyDistance)
{
PreviousTime = KeyTime;
ClosestPreviousKeyDistance = CurrentTime - KeyTime;
PreviousKeyFound = true;
}
}
}
if (PreviousKeyFound)
{
Sequencer.SetGlobalTime(PreviousTime);
}
return FReply::Handled();
}
FReply SAnimationOutlinerTreeNode::OnNextKeyClicked()
{
FSequencer& Sequencer = DisplayNode->GetSequencer();
float ClosestNextKeyDistance = MAX_FLT;
float CurrentTime = Sequencer.GetCurrentLocalTime(*Sequencer.GetFocusedMovieScene());
float NextTime = 0;
bool NextKeyFound = false;
TSet<TSharedPtr<IKeyArea>> KeyAreas;
SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas);
for (TSharedPtr<IKeyArea> KeyArea : KeyAreas)
{
for (FKeyHandle& KeyHandle : KeyArea->GetUnsortedKeyHandles())
{
float KeyTime = KeyArea->GetKeyTime(KeyHandle);
if (KeyTime > CurrentTime && KeyTime - CurrentTime < ClosestNextKeyDistance)
{
NextTime = KeyTime;
ClosestNextKeyDistance = KeyTime - CurrentTime;
NextKeyFound = true;
}
}
}
if (NextKeyFound)
{
Sequencer.SetGlobalTime(NextTime);
}
return FReply::Handled();
}
FReply SAnimationOutlinerTreeNode::OnAddKeyClicked()
{
FSequencer& Sequencer = DisplayNode->GetSequencer();
float CurrentTime = Sequencer.GetCurrentLocalTime(*Sequencer.GetFocusedMovieScene());
TSet<TSharedPtr<IKeyArea>> KeyAreas;
SequencerHelpers::GetAllKeyAreas(DisplayNode, KeyAreas);
TArray<UMovieSceneSection*> KeyAreaSections;
for (TSharedPtr<IKeyArea> KeyArea : KeyAreas)
{
UMovieSceneSection* OwningSection = KeyArea->GetOwningSection();
KeyAreaSections.Add(OwningSection);
}
UMovieSceneSection* NearestSection = MovieSceneHelpers::FindNearestSectionAtTime(KeyAreaSections, CurrentTime);
if (!NearestSection)
{
return FReply::Unhandled();
}
FScopedTransaction Transaction(LOCTEXT("AddKeys", "Add keys at current time"));
for (TSharedPtr<IKeyArea> KeyArea : KeyAreas)
{
UMovieSceneSection* OwningSection = KeyArea->GetOwningSection();
if (OwningSection == NearestSection)
{
OwningSection->SetFlags(RF_Transactional);
OwningSection->Modify();
KeyArea->AddKeyUnique(CurrentTime);
}
}
return FReply::Handled();
}
void SAnimationOutlinerTreeNode::GetAllDescendantNodes(TSharedPtr<FSequencerDisplayNode> RootNode, TArray<TSharedRef<FSequencerDisplayNode> >& AllNodes)
{
if (!RootNode.IsValid())
{
return;
}
AllNodes.Add(RootNode.ToSharedRef());
const FSequencerDisplayNode* RootNodeC = RootNode.Get();
for (TSharedRef<FSequencerDisplayNode> ChildNode : RootNodeC->GetChildNodes())
{
AllNodes.Add(ChildNode);
GetAllDescendantNodes(ChildNode, AllNodes);
}
}
FReply SAnimationOutlinerTreeNode::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && DisplayNode->IsSelectable() )
{
FSequencer& Sequencer = DisplayNode->GetSequencer();
bool bSelected = Sequencer.GetSelection().IsSelected(DisplayNode.ToSharedRef());
TArray<TSharedPtr<FSequencerDisplayNode> > AffectedNodes;
AffectedNodes.Add(DisplayNode.ToSharedRef());
if (MouseEvent.IsShiftDown())
{
FSequencerNodeTree& ParentTree = DisplayNode->GetParentTree();
const TArray< TSharedRef<FSequencerDisplayNode> >RootNodes = ParentTree.GetRootNodes();
// Get all nodes in order
TArray<TSharedRef<FSequencerDisplayNode> > AllNodes;
for (int32 i = 0; i < RootNodes.Num(); ++i)
{
GetAllDescendantNodes(RootNodes[i], AllNodes);
}
int32 FirstIndexToSelect = INT32_MAX;
int32 LastIndexToSelect = INT32_MIN;
for (int32 ChildIndex = 0; ChildIndex < AllNodes.Num(); ++ChildIndex)
{
TSharedRef<FSequencerDisplayNode> ChildNode = AllNodes[ChildIndex];
if (ChildNode == DisplayNode.ToSharedRef() || Sequencer.GetSelection().IsSelected(ChildNode))
{
if (ChildIndex < FirstIndexToSelect)
{
FirstIndexToSelect = ChildIndex;
}
if (ChildIndex > LastIndexToSelect)
{
LastIndexToSelect = ChildIndex;
}
}
}
if (FirstIndexToSelect != INT32_MAX && LastIndexToSelect != INT32_MIN)
{
for (int32 ChildIndex = FirstIndexToSelect; ChildIndex <= LastIndexToSelect; ++ChildIndex)
{
TSharedRef<FSequencerDisplayNode> ChildNode = AllNodes[ChildIndex];
if (!Sequencer.GetSelection().IsSelected(ChildNode))
{
Sequencer.GetSelection().AddToSelection(ChildNode);
AffectedNodes.Add(ChildNode);
}
}
}
}
else if( MouseEvent.IsControlDown() )
{
// Toggle selection when control is down
if (bSelected)
{
Sequencer.GetSelection().RemoveFromSelection(DisplayNode.ToSharedRef());
}
else
{
Sequencer.GetSelection().AddToSelection(DisplayNode.ToSharedRef());
}
}
else
{
// Deselect the other nodes and select this node.
Sequencer.GetSelection().EmptySelectedOutlinerNodes();
Sequencer.GetSelection().AddToSelection(DisplayNode.ToSharedRef());
}
OnSelectionChanged( AffectedNodes );
return FReply::Handled();
}
return FReply::Unhandled();
}
FReply SAnimationOutlinerTreeNode::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
if( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton )
{
TSharedPtr<SWidget> MenuContent = DisplayNode->OnSummonContextMenu(MyGeometry, MouseEvent);
if (MenuContent.IsValid())
{
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(
AsShared(),
WidgetPath,
MenuContent.ToSharedRef(),
MouseEvent.GetScreenSpacePosition(),
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu )
);
return FReply::Handled().SetUserFocus(MenuContent.ToSharedRef(), EFocusCause::SetDirectly);
}
return FReply::Handled();
}
return FReply::Unhandled();
}
const FSlateBrush* SAnimationOutlinerTreeNode::GetNodeBorderImage() const
{
// Display a highlight when the node is selected
FSequencer& Sequencer = DisplayNode->GetSequencer();
const bool bIsSelected = Sequencer.GetSelection().IsSelected(DisplayNode.ToSharedRef());
if (bIsSelected)
{
if (Sequencer.GetSelection().GetActiveSelection() == FSequencerSelection::EActiveSelection::OutlinerNode)
{
return SelectedBrush;
}
else
{
return SelectedBrushInactive;
}
}
else
{
return NotSelectedBrush;
}
}
FSlateColor SAnimationOutlinerTreeNode::GetForegroundBasedOnSelection() const
{
FSequencer& Sequencer = DisplayNode->GetSequencer();
const bool bIsSelected = Sequencer.GetSelection().IsSelected(DisplayNode.ToSharedRef());
return bIsSelected ? TableRowStyle->SelectedTextColor : TableRowStyle->TextColor;
}
EVisibility SAnimationOutlinerTreeNode::GetExpanderVisibility() const
{
return DisplayNode->GetNumChildren() > 0 ? EVisibility::Visible : EVisibility::Hidden;
}
FText SAnimationOutlinerTreeNode::GetDisplayName() const
{
return DisplayNode->GetDisplayName();
}
void SAnimationOutlinerTreeNode::OnSelectionChanged( TArray<TSharedPtr<FSequencerDisplayNode> > AffectedNodes )
{
TArray<TSharedPtr<FSequencerDisplayNode> > ObjectNodes;
for (int32 NodeIdx = 0; NodeIdx < AffectedNodes.Num(); ++NodeIdx)
{
if( AffectedNodes[NodeIdx]->GetType() == ESequencerNode::Object )
{
ObjectNodes.Add(AffectedNodes[NodeIdx]);
}
}
if (!ObjectNodes.Num())
{
return;
}
if (!Sequencer->IsLevelEditorSequencer())
{
return;
}
// Mark that the user is selecting so that the UI doesn't respond to the selection changes in the following block
TSharedRef<SSequencer> SequencerWidget = StaticCastSharedRef<SSequencer>(Sequencer->GetSequencerWidget());
SequencerWidget->SetUserIsSelecting(true);
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClickingOnActors", "Clicking on Actors"));
bool bActorSelected = false;
const FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys();
bool IsControlDown = ModifierKeys.IsControlDown();
bool IsShiftDown = ModifierKeys.IsShiftDown();
const bool bNotifySelectionChanged = false;
const bool bDeselectBSP = true;
const bool bWarnAboutTooManyActors = false;
GEditor->GetSelectedActors()->Modify();
if (!IsControlDown && !IsShiftDown)
{
GEditor->SelectNone(bNotifySelectionChanged, bDeselectBSP, bWarnAboutTooManyActors);
}
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
// Select objects bound to the object node
for (int32 ObjectIdx = 0; ObjectIdx < ObjectNodes.Num(); ++ObjectIdx)
{
const TSharedPtr<const FObjectBindingNode> ObjectNode = StaticCastSharedPtr<const FObjectBindingNode>( ObjectNodes[ObjectIdx] );
// Get the bound objects
TArray<UObject*> RuntimeObjects;
Sequencer->GetRuntimeObjects( Sequencer->GetFocusedMovieSceneInstance(), ObjectNode->GetObjectBinding(), RuntimeObjects );
if( RuntimeObjects.Num() > 0 )
{
// Select each actor
for( int32 ActorIdx = 0; ActorIdx < RuntimeObjects.Num(); ++ActorIdx )
{
AActor* Actor = Cast<AActor>( RuntimeObjects[ActorIdx] );
if( Actor )
{
bool bSelectActor = true;
if (IsControlDown)
{
bSelectActor = Sequencer->GetSelection().IsSelected(ObjectNodes[ObjectIdx].ToSharedRef());
}
GEditor->SelectActor(Actor, bSelectActor, bNotifySelectionChanged );
bActorSelected = true;
}
}
}
}
GEditor->GetSelectedActors()->EndBatchSelectOperation();
if( bActorSelected )
{
GEditor->NoteSelectionChange();
}
// Unlock the selection so that the sequencer widget can now respond to selection changes in the level
SequencerWidget->SetUserIsSelecting(false);
}
#undef LOCTEXT_NAMESPACE