You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- 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]
524 lines
15 KiB
C++
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 |