You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
1974 lines
60 KiB
C++
1974 lines
60 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "STimelineEditor.h"
|
|
#include "Engine/TimelineTemplate.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Widgets/Input/SSlider.h"
|
|
#include "Widgets/Input/STextComboBox.h"
|
|
#include "SPositiveActionButton.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Curves/CurveFloat.h"
|
|
#include "Curves/CurveLinearColor.h"
|
|
#include "Curves/CurveVector.h"
|
|
#include "Editor.h"
|
|
#include "K2Node_Timeline.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h"
|
|
|
|
#include "BlueprintEditor.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "IAssetTools.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "Dialogs/DlgPickAssetPath.h"
|
|
#include "Widgets/Text/SInlineEditableTextBlock.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Engine/Selection.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "Styling/StyleColors.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "STimelineEditor"
|
|
|
|
static TArray<TSharedPtr<FString>> TickGroupNameStrings;
|
|
static bool TickGroupNamesInitialized = false;
|
|
|
|
namespace TimelineEditorHelpers
|
|
{
|
|
FTTTrackBase* GetTrackFromTimeline(UTimelineTemplate* InTimeline, TSharedPtr<FTimelineEdTrack> InTrack)
|
|
{
|
|
FTTTrackId TrackId = InTimeline->GetDisplayTrackId(InTrack->DisplayIndex);
|
|
FTTTrackBase::ETrackType TrackType = (FTTTrackBase::ETrackType)TrackId.TrackType;
|
|
if (TrackType == FTTTrackBase::TT_Event)
|
|
{
|
|
if (InTimeline->EventTracks.IsValidIndex(TrackId.TrackIndex))
|
|
{
|
|
return &InTimeline->EventTracks[TrackId.TrackIndex];
|
|
}
|
|
}
|
|
else if (TrackType == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
if (InTimeline->FloatTracks.IsValidIndex(TrackId.TrackIndex))
|
|
{
|
|
return &InTimeline->FloatTracks[TrackId.TrackIndex];
|
|
}
|
|
}
|
|
else if (TrackType == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
if (InTimeline->VectorTracks.IsValidIndex(TrackId.TrackIndex))
|
|
{
|
|
return &InTimeline->VectorTracks[TrackId.TrackIndex];
|
|
}
|
|
}
|
|
else if (TrackType == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
if (InTimeline->LinearColorTracks.IsValidIndex(TrackId.TrackIndex))
|
|
{
|
|
return &InTimeline->LinearColorTracks[TrackId.TrackIndex];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FName GetTrackNameFromTimeline(UTimelineTemplate* InTimeline, TSharedPtr<FTimelineEdTrack> InTrack)
|
|
{
|
|
FTTTrackBase* TrackBase = GetTrackFromTimeline(InTimeline, InTrack);
|
|
if (TrackBase)
|
|
{
|
|
return TrackBase->GetTrackName();
|
|
}
|
|
return NAME_None;
|
|
}
|
|
|
|
TSubclassOf<UCurveBase> TrackTypeToAllowedClass(FTTTrackBase::ETrackType TrackType)
|
|
{
|
|
switch (TrackType)
|
|
{
|
|
case FTTTrackBase::TT_Event:
|
|
case FTTTrackBase::TT_FloatInterp:
|
|
return UCurveFloat::StaticClass();
|
|
case FTTTrackBase::TT_VectorInterp:
|
|
return UCurveVector::StaticClass();
|
|
case FTTTrackBase::TT_LinearColorInterp:
|
|
return UCurveLinearColor::StaticClass();
|
|
default:
|
|
return UCurveBase::StaticClass();
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// STimelineEdTrack
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void STimelineEdTrack::Construct(const FArguments& InArgs, TSharedPtr<FTimelineEdTrack> InTrack, TSharedPtr<STimelineEditor> InTimelineEd)
|
|
{
|
|
Track = InTrack;
|
|
TimelineEdPtr = InTimelineEd;
|
|
|
|
ResetExternalCurveInfo();
|
|
|
|
// Get the timeline we are editing
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
|
|
// Get a pointer to the track this widget is for
|
|
CurveBasePtr = NULL;
|
|
FTTTrackBase* TrackBase = NULL;
|
|
bool bDrawCurve = true;
|
|
|
|
FTTTrackId TrackId = TimelineObj->GetDisplayTrackId(InTrack->DisplayIndex);
|
|
FTTTrackBase::ETrackType TrackType = (FTTTrackBase::ETrackType)TrackId.TrackType;
|
|
|
|
if(TrackType == FTTTrackBase::TT_Event)
|
|
{
|
|
check(TrackId.TrackIndex < TimelineObj->EventTracks.Num());
|
|
FTTEventTrack* EventTrack = &(TimelineObj->EventTracks[TrackId.TrackIndex]);
|
|
CurveBasePtr = EventTrack->CurveKeys;
|
|
TrackBase = EventTrack;
|
|
bDrawCurve = false;
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
check(TrackId.TrackIndex < TimelineObj->FloatTracks.Num());
|
|
FTTFloatTrack* FloatTrack = &(TimelineObj->FloatTracks[TrackId.TrackIndex]);
|
|
CurveBasePtr = FloatTrack->CurveFloat;
|
|
TrackBase = FloatTrack;
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
check(TrackId.TrackIndex < TimelineObj->VectorTracks.Num());
|
|
FTTVectorTrack* VectorTrack = &(TimelineObj->VectorTracks[TrackId.TrackIndex]);
|
|
CurveBasePtr = VectorTrack->CurveVector;
|
|
TrackBase = VectorTrack;
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
check(TrackId.TrackIndex < TimelineObj->LinearColorTracks.Num());
|
|
FTTLinearColorTrack* LinearColorTrack = &(TimelineObj->LinearColorTracks[TrackId.TrackIndex]);
|
|
CurveBasePtr = LinearColorTrack->CurveLinearColor;
|
|
TrackBase = LinearColorTrack;
|
|
}
|
|
|
|
if( TrackBase && TrackBase->bIsExternalCurve )
|
|
{
|
|
//Update track with external curve info
|
|
UseExternalCurve( CurveBasePtr );
|
|
}
|
|
|
|
TSharedRef<STimelineEditor> TimelineRef = TimelineEd.ToSharedRef();
|
|
TSharedPtr<SInlineEditableTextBlock> InlineTextBlock;
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
// Heading Slot
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop_Hovered"))
|
|
.ForegroundColor(FLinearColor::White)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Expander Button
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &STimelineEdTrack::GetIsExpandedState)
|
|
.OnCheckStateChanged(this, &STimelineEdTrack::OnIsExpandedStateChanged)
|
|
.CheckedImage(FAppStyle::GetBrush("TreeArrow_Expanded"))
|
|
.CheckedHoveredImage(FAppStyle::GetBrush("TreeArrow_Expanded_Hovered"))
|
|
.CheckedPressedImage(FAppStyle::GetBrush("TreeArrow_Expanded"))
|
|
.UncheckedImage(FAppStyle::GetBrush("TreeArrow_Collapsed"))
|
|
.UncheckedHoveredImage(FAppStyle::GetBrush("TreeArrow_Collapsed_Hovered"))
|
|
.UncheckedPressedImage(FAppStyle::GetBrush("TreeArrow_Collapsed"))
|
|
]
|
|
|
|
// Track Name
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
[
|
|
// Name of track
|
|
SAssignNew(InlineTextBlock, SInlineEditableTextBlock)
|
|
.Text(FText::FromName(TrackBase->GetTrackName()))
|
|
.ToolTipText(LOCTEXT("TrackNameTooltip", "Enter track name"))
|
|
.OnVerifyTextChanged(TimelineRef, &STimelineEditor::OnVerifyTrackNameCommit, TrackBase, this)
|
|
.OnTextCommitted(TimelineRef, &STimelineEditor::OnTrackNameCommitted, TrackBase, this)
|
|
]
|
|
]
|
|
]
|
|
|
|
// Content Slot
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
// Box for content visibility
|
|
SNew(SBox)
|
|
.Visibility(this, &STimelineEdTrack::GetContentVisibility)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Label Area
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SVerticalBox)
|
|
// External Curve Label
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ExternalCurveLabel", "External Curve"))
|
|
.ColorAndOpacity(FStyleColors::Foreground)
|
|
]
|
|
|
|
// External Curve Controls
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2, 0, 0, 4)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FAppStyle::GetBrush("NoBrush"))
|
|
.ForegroundColor(FStyleColors::Foreground)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
[
|
|
SNew(SObjectPropertyEntryBox)
|
|
.AllowedClass(TimelineEditorHelpers::TrackTypeToAllowedClass(TrackType))
|
|
.ObjectPath(this, &STimelineEdTrack::GetExternalCurvePath)
|
|
.OnObjectChanged(FOnSetObject::CreateSP(this, &STimelineEdTrack::OnChooseCurve))
|
|
]
|
|
|
|
// Convert to internal curve button
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
|
|
.OnClicked(this, &STimelineEdTrack::OnClickClear)
|
|
.ContentPadding(1.f)
|
|
.ToolTipText(NSLOCTEXT("TimelineEdTrack", "TimelineEdTrack_Clear", "Convert to Internal Curve"))
|
|
[
|
|
SNew(SImage)
|
|
.Image( FAppStyle::GetBrush(TEXT("PropertyWindow.Button_Clear")))
|
|
.ColorAndOpacity(FStyleColors::Foreground)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
|
|
// Synchronize curve view checkbox.
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2, 0, 2, 0)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(this, &STimelineEdTrack::GetIsCurveViewSynchronizedState)
|
|
.OnCheckStateChanged(this, &STimelineEdTrack::OnIsCurveViewSynchronizedStateChanged)
|
|
.ToolTipText(LOCTEXT("SynchronizeViewToolTip", "Keep the zoom and pan of this curve synchronized with other curves."))
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("SynchronizeViewLabel", "Synchronize View"))
|
|
.ColorAndOpacity(FStyleColors::Foreground)
|
|
]
|
|
]
|
|
|
|
// Re-ordering timeline tracks.
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(2, 0, 2, 0)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
|
|
.OnClicked(this, &STimelineEdTrack::OnMoveUp)
|
|
.IsEnabled(this, &STimelineEdTrack::CanMoveUp)
|
|
.ContentPadding(1.f)
|
|
.ToolTipText(NSLOCTEXT("TimelineEdTrack", "TimelineEdTrack_MoveUp", "Move track up list"))
|
|
[
|
|
SNew(SImage)
|
|
.Image( FAppStyle::GetBrush(TEXT("ArrowUp")) )
|
|
.ColorAndOpacity(FStyleColors::Foreground)
|
|
]
|
|
]
|
|
|
|
// Convert to internal curve button
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
|
|
.OnClicked(this, &STimelineEdTrack::OnMoveDown)
|
|
.IsEnabled(this, &STimelineEdTrack::CanMoveDown)
|
|
.ContentPadding(1.f)
|
|
.ToolTipText(NSLOCTEXT("TimelineEdTrack", "TimelineEdTrack_MoveDown", "Move track down list"))
|
|
[
|
|
SNew(SImage)
|
|
.Image( FAppStyle::GetBrush(TEXT("ArrowDown")) )
|
|
.ColorAndOpacity(FStyleColors::Foreground)
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
.HAlign(HAlign_Left)
|
|
.Padding(2)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ReorderLabel", "Reorder"))
|
|
.ColorAndOpacity(FStyleColors::Foreground)
|
|
]
|
|
]
|
|
]
|
|
|
|
// Graph Area
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1)
|
|
[
|
|
SNew(SBorder)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
SAssignNew(TrackWidget, SCurveEditor)
|
|
.ViewMinInput(this, &STimelineEdTrack::GetMinInput)
|
|
.ViewMaxInput(this, &STimelineEdTrack::GetMaxInput)
|
|
.ViewMinOutput(this, &STimelineEdTrack::GetMinOutput)
|
|
.ViewMaxOutput(this, &STimelineEdTrack::GetMaxOutput)
|
|
.TimelineLength(TimelineRef, &STimelineEditor::GetTimelineLength)
|
|
.OnSetInputViewRange(this, &STimelineEdTrack::OnSetInputViewRange)
|
|
.OnSetOutputViewRange(this, &STimelineEdTrack::OnSetOutputViewRange)
|
|
.DesiredSize(TimelineRef, &STimelineEditor::GetTimelineDesiredSize)
|
|
.DrawCurve(bDrawCurve)
|
|
.HideUI(false)
|
|
.OnCreateAsset(this, &STimelineEdTrack::OnCreateExternalCurve )
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
if( TrackBase )
|
|
{
|
|
bool bZoomToFit = false;
|
|
if((GetMaxInput() == 0) && (GetMinInput() == 0))
|
|
{
|
|
// If the input range has not been set, zoom to fit to set it
|
|
bZoomToFit = true;
|
|
}
|
|
|
|
//Inform track widget about the curve and whether it is editable or not.
|
|
TrackWidget->SetZoomToFit(bZoomToFit, bZoomToFit);
|
|
TrackWidget->SetCurveOwner(CurveBasePtr, !TrackBase->bIsExternalCurve);
|
|
|
|
// In case the user has disabled auto frame in their settings, make sure to still adjust the zoom if we don't have an input
|
|
// range yet.
|
|
if (!TrackWidget->GetAutoFrame() && bZoomToFit)
|
|
{
|
|
TrackWidget->ZoomToFitVertical();
|
|
TrackWidget->ZoomToFitHorizontal();
|
|
}
|
|
}
|
|
|
|
InTrack->OnRenameRequest.BindSP(InlineTextBlock.Get(), &SInlineEditableTextBlock::EnterEditingMode);
|
|
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
FString STimelineEdTrack::CreateUniqueCurveAssetPathName()
|
|
{
|
|
//Default path
|
|
FString BasePath = FString(TEXT( "/Game/Unsorted" ));
|
|
|
|
TSharedRef<STimelineEditor> TimelineRef = TimelineEdPtr.Pin().ToSharedRef();
|
|
|
|
//Get curve name from editable text box
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
|
|
// Create a unique asset name so the user can instantly hit OK if they want to create the new asset
|
|
FString AssetName = TimelineEditorHelpers::GetTrackNameFromTimeline(TimelineEdPtr.Pin()->GetTimeline(), Track).ToString();
|
|
FString PackageName;
|
|
BasePath = BasePath + TEXT("/") + AssetName;
|
|
AssetToolsModule.Get().CreateUniqueAssetName(BasePath, TEXT(""), PackageName, AssetName);
|
|
|
|
return PackageName;
|
|
}
|
|
|
|
void STimelineEdTrack::OnCloseCreateCurveWindow()
|
|
{
|
|
if(AssetCreationWindow.IsValid())
|
|
{
|
|
//Destroy asset creation dialog
|
|
TSharedPtr<SWindow> ParentWindow = AssetCreationWindow->GetParentWindow();
|
|
AssetCreationWindow->RequestDestroyWindow();
|
|
AssetCreationWindow.Reset();
|
|
}
|
|
}
|
|
|
|
void STimelineEdTrack::OnCreateExternalCurve()
|
|
{
|
|
UCurveBase* NewCurveAsset = CreateCurveAsset();
|
|
if( NewCurveAsset )
|
|
{
|
|
//Switch internal to external curve
|
|
SwitchToExternalCurve(NewCurveAsset);
|
|
}
|
|
//Close dialog once switching is complete
|
|
OnCloseCreateCurveWindow();
|
|
}
|
|
|
|
void STimelineEdTrack::SwitchToExternalCurve(UCurveBase* AssetCurvePtr)
|
|
{
|
|
if( AssetCurvePtr )
|
|
{
|
|
// Get the timeline we are editing
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
|
|
FTTTrackId TrackId = TimelineObj->GetDisplayTrackId(Track->DisplayIndex);
|
|
FTTTrackBase::ETrackType TrackType = (FTTTrackBase::ETrackType)TrackId.TrackType;
|
|
|
|
FTTTrackBase* TrackBase = NULL;
|
|
if(TrackType == FTTTrackBase::TT_Event)
|
|
{
|
|
if(AssetCurvePtr->IsA(UCurveFloat::StaticClass()))
|
|
{
|
|
FTTEventTrack& NewTrack = TimelineObj->EventTracks[ TrackId.TrackIndex ];
|
|
NewTrack.CurveKeys = Cast<UCurveFloat>(AssetCurvePtr);
|
|
TrackBase = &NewTrack;
|
|
}
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
if(AssetCurvePtr->IsA(UCurveFloat::StaticClass()))
|
|
{
|
|
FTTFloatTrack& NewTrack = TimelineObj->FloatTracks[ TrackId.TrackIndex ];
|
|
NewTrack.CurveFloat = Cast<UCurveFloat>(AssetCurvePtr);
|
|
TrackBase = &NewTrack;
|
|
}
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
if(AssetCurvePtr->IsA(UCurveVector::StaticClass()))
|
|
{
|
|
FTTVectorTrack& NewTrack = TimelineObj->VectorTracks[ TrackId.TrackIndex ];
|
|
NewTrack.CurveVector = Cast<UCurveVector>(AssetCurvePtr);
|
|
TrackBase = &NewTrack;
|
|
}
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
if(AssetCurvePtr->IsA(UCurveLinearColor::StaticClass()))
|
|
{
|
|
FTTLinearColorTrack& NewTrack = TimelineObj->LinearColorTracks[ TrackId.TrackIndex ];
|
|
NewTrack.CurveLinearColor = Cast<UCurveLinearColor>(AssetCurvePtr);
|
|
TrackBase = &NewTrack;
|
|
}
|
|
}
|
|
|
|
if( TrackBase )
|
|
{
|
|
//Flag it as using external curve
|
|
TrackBase->bIsExternalCurve = true;
|
|
TrackWidget->SetCurveOwner( AssetCurvePtr, false );
|
|
CurveBasePtr = AssetCurvePtr;
|
|
|
|
UseExternalCurve(CurveBasePtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void STimelineEdTrack::UseExternalCurve( UObject* AssetObj )
|
|
{
|
|
if (AssetObj)
|
|
{
|
|
ExternalCurvePath = AssetObj->GetPathName();
|
|
}
|
|
else
|
|
{
|
|
ResetExternalCurveInfo();
|
|
}
|
|
}
|
|
|
|
|
|
void STimelineEdTrack::UseInternalCurve( )
|
|
{
|
|
if( CurveBasePtr )
|
|
{
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
|
|
FTTTrackId TrackId = TimelineObj->GetDisplayTrackId(Track->DisplayIndex);
|
|
FTTTrackBase::ETrackType TrackType = (FTTTrackBase::ETrackType)TrackId.TrackType;
|
|
|
|
FTTTrackBase* TrackBase = NULL;
|
|
UCurveBase* CurveBase = NULL;
|
|
|
|
if(TrackType == FTTTrackBase::TT_Event)
|
|
{
|
|
FTTEventTrack& NewTrack = TimelineObj->EventTracks[ TrackId.TrackIndex ];
|
|
|
|
if(NewTrack.bIsExternalCurve )
|
|
{
|
|
UCurveFloat* SrcCurve = NewTrack.CurveKeys;
|
|
UCurveFloat* DestCurve = Cast<UCurveFloat>(TimelineEd->CreateNewCurve( TrackType) );
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
//Copy external event curve data to internal curve
|
|
CopyCurveData( &SrcCurve->FloatCurve, &DestCurve->FloatCurve );
|
|
NewTrack.CurveKeys = DestCurve;
|
|
CurveBase = DestCurve;
|
|
}
|
|
}
|
|
TrackBase = &NewTrack;
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
FTTFloatTrack& NewTrack = TimelineObj->FloatTracks[ TrackId.TrackIndex ];
|
|
if(NewTrack.bIsExternalCurve)
|
|
{
|
|
UCurveFloat* SrcCurve = NewTrack.CurveFloat;
|
|
UCurveFloat* DestCurve = Cast<UCurveFloat>(TimelineEd->CreateNewCurve( TrackType) );
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
//Copy external float curve data to internal curve
|
|
CopyCurveData( &SrcCurve->FloatCurve, &DestCurve->FloatCurve );
|
|
NewTrack.CurveFloat = DestCurve;
|
|
CurveBase = DestCurve;
|
|
}
|
|
}
|
|
TrackBase = &NewTrack;
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
FTTVectorTrack& NewTrack = TimelineObj->VectorTracks[ TrackId.TrackIndex ];
|
|
if(NewTrack.bIsExternalCurve )
|
|
{
|
|
UCurveVector* SrcCurve = NewTrack.CurveVector;
|
|
UCurveVector* DestCurve = Cast<UCurveVector>(TimelineEd->CreateNewCurve( TrackType) );
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
for( int32 i=0; i<3; i++ )
|
|
{
|
|
//Copy external vector curve data to internal curve
|
|
CopyCurveData( &SrcCurve->FloatCurves[i], &DestCurve->FloatCurves[i] );
|
|
}
|
|
NewTrack.CurveVector = DestCurve;
|
|
CurveBase = DestCurve;
|
|
}
|
|
}
|
|
TrackBase = &NewTrack;
|
|
}
|
|
else if(TrackType == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
FTTLinearColorTrack& NewTrack = TimelineObj->LinearColorTracks[ TrackId.TrackIndex ];
|
|
if(NewTrack.bIsExternalCurve )
|
|
{
|
|
UCurveLinearColor* SrcCurve = NewTrack.CurveLinearColor;
|
|
UCurveLinearColor* DestCurve = Cast<UCurveLinearColor>(TimelineEd->CreateNewCurve( TrackType) );
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
for( int32 i=0; i<4; i++ )
|
|
{
|
|
//Copy external vector curve data to internal curve
|
|
CopyCurveData( &SrcCurve->FloatCurves[i], &DestCurve->FloatCurves[i] );
|
|
}
|
|
NewTrack.CurveLinearColor = DestCurve;
|
|
CurveBase = DestCurve;
|
|
}
|
|
}
|
|
TrackBase = &NewTrack;
|
|
}
|
|
|
|
if( TrackBase && CurveBase )
|
|
{
|
|
//Reset flag
|
|
TrackBase->bIsExternalCurve = false;
|
|
|
|
TrackWidget->SetCurveOwner( CurveBase );
|
|
CurveBasePtr = CurveBase;
|
|
|
|
ResetExternalCurveInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply STimelineEdTrack::OnClickClear()
|
|
{
|
|
UseInternalCurve();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void STimelineEdTrack::OnChooseCurve(const FAssetData& InObject)
|
|
{
|
|
UCurveBase* SelectedObj = Cast<UCurveBase>(InObject.GetAsset());
|
|
if (SelectedObj)
|
|
{
|
|
SwitchToExternalCurve(SelectedObj);
|
|
}
|
|
else
|
|
{
|
|
UseInternalCurve();
|
|
}
|
|
}
|
|
|
|
FString STimelineEdTrack::GetExternalCurvePath( ) const
|
|
{
|
|
return ExternalCurvePath;
|
|
}
|
|
|
|
UCurveBase* STimelineEdTrack::CreateCurveAsset()
|
|
{
|
|
UCurveBase* AssetCurve = NULL;
|
|
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
|
|
FTTTrackId TrackId = TimelineObj->GetDisplayTrackId(Track->DisplayIndex);
|
|
FTTTrackBase::ETrackType TrackType = (FTTTrackBase::ETrackType)TrackId.TrackType;
|
|
|
|
if( TrackWidget.IsValid() )
|
|
{
|
|
TSharedRef<SDlgPickAssetPath> NewLayerDlg =
|
|
SNew(SDlgPickAssetPath)
|
|
.Title(LOCTEXT("CreateExternalCurve", "Create External Curve"))
|
|
.DefaultAssetPath(FText::FromString(CreateUniqueCurveAssetPathName()));
|
|
|
|
if (NewLayerDlg->ShowModal() != EAppReturnType::Cancel)
|
|
{
|
|
FString PackageName = NewLayerDlg->GetFullAssetPath().ToString();
|
|
FName AssetName = FName(*NewLayerDlg->GetAssetName().ToString());
|
|
|
|
UPackage* Package = CreatePackage( *PackageName);
|
|
|
|
//Get the curve class type
|
|
TSubclassOf<UCurveBase> CurveType;
|
|
|
|
if( TrackType == FTTTrackBase::TT_Event || TrackType == FTTTrackBase::TT_FloatInterp )
|
|
{
|
|
CurveType = UCurveFloat::StaticClass();
|
|
}
|
|
else if( TrackType == FTTTrackBase::TT_LinearColorInterp )
|
|
{
|
|
CurveType = UCurveLinearColor::StaticClass();
|
|
}
|
|
else
|
|
{
|
|
CurveType = UCurveVector::StaticClass();
|
|
}
|
|
|
|
//Create curve object
|
|
UObject* NewObj = TrackWidget->CreateCurveObject( CurveType, Package, AssetName );
|
|
if( NewObj )
|
|
{
|
|
//Copy curve data from current curve to newly create curve
|
|
if( TrackType == FTTTrackBase::TT_Event || TrackType == FTTTrackBase::TT_FloatInterp )
|
|
{
|
|
UCurveFloat* DestCurve = CastChecked<UCurveFloat>(NewObj);
|
|
|
|
AssetCurve = DestCurve;
|
|
UCurveFloat* SourceCurve = CastChecked<UCurveFloat>(CurveBasePtr);
|
|
|
|
if( SourceCurve && DestCurve )
|
|
{
|
|
CopyCurveData( &SourceCurve->FloatCurve, &DestCurve->FloatCurve );
|
|
}
|
|
|
|
DestCurve->bIsEventCurve = ( TrackType == FTTTrackBase::TT_Event ) ? true : false;
|
|
}
|
|
else if( TrackType == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
UCurveVector* DestCurve = Cast<UCurveVector>(NewObj);
|
|
|
|
AssetCurve = DestCurve;
|
|
UCurveVector* SrcCurve = CastChecked<UCurveVector>(CurveBasePtr);
|
|
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
for( int32 i=0; i<3; i++ )
|
|
{
|
|
CopyCurveData( &SrcCurve->FloatCurves[i], &DestCurve->FloatCurves[i] );
|
|
}
|
|
}
|
|
}
|
|
else if( TrackType == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
UCurveLinearColor* DestCurve = Cast<UCurveLinearColor>(NewObj);
|
|
|
|
AssetCurve = DestCurve;
|
|
UCurveLinearColor* SrcCurve = CastChecked<UCurveLinearColor>(CurveBasePtr);
|
|
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
for( int32 i=0; i<4; i++ )
|
|
{
|
|
CopyCurveData( &SrcCurve->FloatCurves[i], &DestCurve->FloatCurves[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the new objects as the sole selection.
|
|
USelection* SelectionSet = GEditor->GetSelectedObjects();
|
|
SelectionSet->DeselectAll();
|
|
SelectionSet->Select( NewObj );
|
|
|
|
// Notify the asset registry
|
|
FAssetRegistryModule::AssetCreated(NewObj);
|
|
|
|
// Mark the package dirty...
|
|
Package->GetOutermost()->MarkPackageDirty();
|
|
return AssetCurve;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void STimelineEdTrack::CopyCurveData( const FRichCurve* SrcCurve, FRichCurve* DestCurve )
|
|
{
|
|
if( SrcCurve && DestCurve )
|
|
{
|
|
for (auto It(SrcCurve->GetKeyIterator()); It; ++It)
|
|
{
|
|
const FRichCurveKey& Key = *It;
|
|
FKeyHandle KeyHandle = DestCurve->AddKey(Key.Time, Key.Value);
|
|
DestCurve->GetKey(KeyHandle) = Key;
|
|
}
|
|
}
|
|
}
|
|
|
|
ECheckBoxState STimelineEdTrack::GetIsExpandedState() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
|
|
return (TrackBase && TrackBase->bIsExpanded) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEdTrack::OnIsExpandedStateChanged(ECheckBoxState IsExpandedState)
|
|
{
|
|
FTTTrackBase* TrackBase = GetTrackBase();
|
|
|
|
if (TrackBase)
|
|
{
|
|
TrackBase->bIsExpanded = IsExpandedState == ECheckBoxState::Checked;
|
|
}
|
|
|
|
//recalculate how much space the widgets take up to enable scrolling when needed
|
|
TSharedPtr<STimelineEditor> TimelineEditor = TimelineEdPtr.Pin();
|
|
TimelineEditor->OnTimelineChanged();
|
|
}
|
|
|
|
EVisibility STimelineEdTrack::GetContentVisibility() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
|
|
return (TrackBase && TrackBase->bIsExpanded) ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
ECheckBoxState STimelineEdTrack::GetIsCurveViewSynchronizedState() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
|
|
return (TrackBase && TrackBase->bIsCurveViewSynchronized) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEdTrack::OnIsCurveViewSynchronizedStateChanged(ECheckBoxState IsCurveViewSynchronizedState)
|
|
{
|
|
FTTTrackBase* TrackBase = GetTrackBase();
|
|
|
|
if (TrackBase)
|
|
{
|
|
TrackBase->bIsCurveViewSynchronized = IsCurveViewSynchronizedState == ECheckBoxState::Checked;
|
|
}
|
|
|
|
//local is always up to date, make sure the timeline editor is inited at least once
|
|
TSharedPtr<STimelineEditor> TimelineEditor = TimelineEdPtr.Pin();
|
|
if ((TimelineEditor->GetViewMaxInput() == 0) && (TimelineEditor->GetViewMinInput() == 0))
|
|
{
|
|
//we've never used the shared timeline range, but our local one is always up to date!
|
|
TimelineEditor->SetInputViewRange(LocalInputMin, LocalInputMax);
|
|
TimelineEditor->SetOutputViewRange(LocalOutputMin, LocalOutputMax);
|
|
}
|
|
//only take the timeline editors extents if we are accepting synchronization
|
|
if ((TrackBase && TrackBase->bIsCurveViewSynchronized) || ((LocalInputMax == 0.0f) && (LocalInputMin == 0.0f)))
|
|
{
|
|
LocalInputMin = TimelineEditor->GetViewMinInput();
|
|
LocalInputMax = TimelineEditor->GetViewMaxInput();
|
|
LocalOutputMin = TimelineEditor->GetViewMinOutput();
|
|
LocalOutputMax = TimelineEditor->GetViewMaxOutput();
|
|
}
|
|
}
|
|
|
|
FReply STimelineEdTrack::OnMoveUp()
|
|
{
|
|
MoveTrack(-1);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
bool STimelineEdTrack::CanMoveUp() const
|
|
{
|
|
return (Track->DisplayIndex > 0);
|
|
return false;
|
|
}
|
|
FReply STimelineEdTrack::OnMoveDown()
|
|
{
|
|
MoveTrack(1);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
bool STimelineEdTrack::CanMoveDown() const
|
|
{
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
|
|
return (Track->DisplayIndex < (TimelineObj->GetNumDisplayTracks() - 1));
|
|
}
|
|
|
|
void STimelineEdTrack::MoveTrack(int32 DirectionDelta)
|
|
{
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
|
|
TimelineEd->OnReorderTracks(Track->DisplayIndex, DirectionDelta);
|
|
}
|
|
|
|
|
|
float STimelineEdTrack::GetMinInput() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
return (TrackBase && TrackBase->bIsCurveViewSynchronized)
|
|
? TimelineEdPtr.Pin()->GetViewMinInput()
|
|
: LocalInputMin;
|
|
}
|
|
|
|
float STimelineEdTrack::GetMaxInput() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
return (TrackBase && TrackBase->bIsCurveViewSynchronized)
|
|
? TimelineEdPtr.Pin()->GetViewMaxInput()
|
|
: LocalInputMax;
|
|
}
|
|
|
|
float STimelineEdTrack::GetMinOutput() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
return (TrackBase && TrackBase->bIsCurveViewSynchronized)
|
|
? TimelineEdPtr.Pin()->GetViewMinOutput()
|
|
: LocalOutputMin;
|
|
}
|
|
|
|
float STimelineEdTrack::GetMaxOutput() const
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
return (TrackBase && TrackBase->bIsCurveViewSynchronized)
|
|
? TimelineEdPtr.Pin()->GetViewMaxOutput()
|
|
: LocalOutputMax;
|
|
}
|
|
|
|
void STimelineEdTrack::OnSetInputViewRange(float Min, float Max)
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
if (TrackBase && TrackBase->bIsCurveViewSynchronized)
|
|
{
|
|
TimelineEdPtr.Pin()->SetInputViewRange(Min, Max);
|
|
}
|
|
//always set these in case we go back and forth
|
|
LocalInputMin = Min;
|
|
LocalInputMax = Max;
|
|
}
|
|
|
|
void STimelineEdTrack::OnSetOutputViewRange(float Min, float Max)
|
|
{
|
|
const FTTTrackBase* TrackBase = GetTrackBase();
|
|
if (TrackBase && TrackBase->bIsCurveViewSynchronized)
|
|
{
|
|
TimelineEdPtr.Pin()->SetOutputViewRange(Min, Max);
|
|
}
|
|
//always set these in case we go back and forth
|
|
LocalOutputMin = Min;
|
|
LocalOutputMax = Max;
|
|
}
|
|
|
|
void STimelineEdTrack::ResetExternalCurveInfo( )
|
|
{
|
|
ExternalCurvePath = FString( TEXT( "None" ) );
|
|
}
|
|
|
|
FTTTrackBase* STimelineEdTrack::GetTrackBase()
|
|
{
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
FTTTrackBase* TrackBase = TimelineEditorHelpers::GetTrackFromTimeline(TimelineObj, Track);
|
|
return TrackBase;
|
|
}
|
|
const FTTTrackBase* STimelineEdTrack::GetTrackBase() const
|
|
{
|
|
TSharedPtr<STimelineEditor> TimelineEd = TimelineEdPtr.Pin();
|
|
check(TimelineEd.IsValid());
|
|
UTimelineTemplate* TimelineObj = TimelineEd->GetTimeline();
|
|
check(TimelineObj); // We shouldn't have any tracks if there is no track object!
|
|
FTTTrackBase* TrackBase = TimelineEditorHelpers::GetTrackFromTimeline(TimelineObj, Track);
|
|
return TrackBase;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// STimelineEditor
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void STimelineEditor::Construct(const FArguments& InArgs, TSharedPtr<FBlueprintEditor> InKismet2, UTimelineTemplate* InTimelineObj)
|
|
{
|
|
NewTrackPendingRename = NAME_None;
|
|
|
|
Kismet2Ptr = InKismet2;
|
|
TimelineObj = NULL;
|
|
|
|
NominalTimelineDesiredHeight = 300.0f;
|
|
TimelineDesiredSize = FVector2D(128.0f, NominalTimelineDesiredHeight);
|
|
|
|
// Leave these uninitialized at first. We'll zoom to fit the tracks which will set the correct values
|
|
ViewMinInput = 0.f;
|
|
ViewMaxInput = 0.f;
|
|
ViewMinOutput = 0.f;
|
|
ViewMaxOutput = 0.f;
|
|
|
|
CommandList = MakeShareable( new FUICommandList );
|
|
|
|
CommandList->MapAction( FGenericCommands::Get().Rename,
|
|
FExecuteAction::CreateSP(this, &STimelineEditor::OnRequestTrackRename),
|
|
FCanExecuteAction::CreateSP(this, &STimelineEditor::CanRenameSelectedTrack) );
|
|
|
|
CommandList->MapAction( FGenericCommands::Get().Delete,
|
|
FExecuteAction::CreateSP(this, &STimelineEditor::OnDeleteSelectedTracks),
|
|
FCanExecuteAction::CreateSP(this, &STimelineEditor::CanDeleteSelectedTracks) );
|
|
|
|
// Get TickGroup enum info for the TimelineEditor control panel
|
|
int32 CurrentTickGroupNameStringIndex = 0;
|
|
const UEnum* TickGroupEnum = StaticEnum<ETickingGroup>();
|
|
if (!TickGroupNamesInitialized && TickGroupEnum)
|
|
{
|
|
// Store the TickGroup name info one time, in one place accessible to all TimelineEditors
|
|
TickGroupNameStrings.Empty();
|
|
for (int32 TickGroupIndex = 0; TickGroupIndex < TickGroupEnum->NumEnums() - 1; TickGroupIndex++)
|
|
{
|
|
if (!TickGroupEnum->HasMetaData(TEXT("Hidden"), TickGroupIndex))
|
|
{
|
|
TickGroupNameStrings.Add(MakeShareable(new FString(TickGroupEnum->GetNameStringByIndex(TickGroupIndex))));
|
|
}
|
|
}
|
|
TickGroupNamesInitialized = true;
|
|
}
|
|
if (TickGroupNamesInitialized && InTimelineObj)
|
|
{
|
|
// Set the current index into the TickGroupNameStrings so the ComboBox being set up below can highlight the current value
|
|
FString CurrentTickGroupNameString = TickGroupEnum->GetNameStringByValue((int64)InTimelineObj->TimelineTickGroup);
|
|
CurrentTickGroupNameStringIndex = TickGroupNameStrings.IndexOfByPredicate([CurrentTickGroupNameString](const TSharedPtr<FString> NameString)
|
|
{
|
|
return *NameString.Get() == CurrentTickGroupNameString;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// If we don't have the ETickingGroup enum available for some reason, don't crash the Editor
|
|
TickGroupNameStrings.Empty();
|
|
TickGroupNameStrings.Add(MakeShareable(new FString(TEXT("EnumNotReady"))));
|
|
}
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
// Header, shows name of timeline we are editing
|
|
SNew(SBorder)
|
|
. BorderImage( FAppStyle::GetBrush( TEXT("Graph.TitleBackground") ) )
|
|
. HAlign(HAlign_Center)
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.Title"))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding( 10,0 )
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.Image( FAppStyle::GetBrush(TEXT("GraphEditor.TimelineGlyph")) )
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
. VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font( FCoreStyle::GetDefaultFontStyle("Regular", 14) )
|
|
.ColorAndOpacity( FLinearColor(1,1,1,0.5) )
|
|
.Text( this, &STimelineEditor::GetTimelineName )
|
|
]
|
|
]
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
// Box for holding buttons
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(6.f)
|
|
[
|
|
SNew(SPositiveActionButton)
|
|
.OnGetMenuContent(this, &STimelineEditor::MakeAddButton)
|
|
.Icon(FAppStyle::Get().GetBrush("Icons.Plus"))
|
|
.Text(LOCTEXT("Track", "Track"))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Length label
|
|
SNew(STextBlock)
|
|
.Text( LOCTEXT( "Length", "Length" ) )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(6.0f, 2.0f, 2.0f, 2.0f))
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Length edit box
|
|
SAssignNew(TimelineLengthEdit, SEditableTextBox)
|
|
.Text( this, &STimelineEditor::GetLengthString )
|
|
.OnTextCommitted( this, &STimelineEditor::OnLengthStringChanged )
|
|
.SelectAllTextWhenFocused(true)
|
|
.MinDesiredWidth(64)
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.Length"))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Use last keyframe as length check box
|
|
SAssignNew(UseLastKeyframeCheckBox, SCheckBox)
|
|
.IsChecked( this, &STimelineEditor::IsUseLastKeyframeChecked )
|
|
.OnCheckStateChanged( this, &STimelineEditor::OnUseLastKeyframeChanged )
|
|
.Style(FAppStyle::Get(), "ToggleButtonCheckbox")
|
|
.ToolTipText(LOCTEXT("UseLastKeyframe", "Use Last Keyframe"))
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("TimelineEditor.UseLastKeyframe"))
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.UseLastKeyframe"))
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Play check box
|
|
SAssignNew(PlayCheckBox, SCheckBox)
|
|
.IsChecked( this, &STimelineEditor::IsAutoPlayChecked )
|
|
.OnCheckStateChanged( this, &STimelineEditor::OnAutoPlayChanged )
|
|
.Style(FAppStyle::Get(), "ToggleButtonCheckbox")
|
|
.ToolTipText(LOCTEXT("AutoPlay", "AutoPlay"))
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("TimelineEditor.AutoPlay"))
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.AutoPlay"))
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Loop check box
|
|
SAssignNew(LoopCheckBox, SCheckBox)
|
|
.IsChecked( this, &STimelineEditor::IsLoopChecked )
|
|
.OnCheckStateChanged( this, &STimelineEditor::OnLoopChanged )
|
|
.Style(FAppStyle::Get(), "ToggleButtonCheckbox")
|
|
.ToolTipText(LOCTEXT("Loop", "Loop"))
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("TimelineEditor.Loop"))
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.Loop"))
|
|
]
|
|
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Replicated check box
|
|
SAssignNew(ReplicatedCheckBox, SCheckBox)
|
|
.IsChecked( this, &STimelineEditor::IsReplicatedChecked )
|
|
.OnCheckStateChanged( this, &STimelineEditor::OnReplicatedChanged )
|
|
.Style(FAppStyle::Get(), "ToggleButtonCheckbox")
|
|
.ToolTipText(LOCTEXT("Replicated", "Replicated"))
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("TimelineEditor.Replicated"))
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.Replicated"))
|
|
]
|
|
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
// Ignore Time Dilation check box
|
|
SAssignNew(IgnoreTimeDilationCheckBox, SCheckBox)
|
|
.IsChecked( this, &STimelineEditor::IsIgnoreTimeDilationChecked )
|
|
.OnCheckStateChanged( this, &STimelineEditor::OnIgnoreTimeDilationChanged )
|
|
.Style(FAppStyle::Get(), "ToggleButtonCheckbox")
|
|
.ToolTipText(LOCTEXT("IgnoreTimeDilation", "Ignore Time Dilation"))
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(FAppStyle::Get().GetBrush("TimelineEditor.IgnoreTimeDilation"))
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.IgnoreTimeDilation"))
|
|
]
|
|
]
|
|
// Tick Group Controls
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("TickGroupLabel", "Tick Group"))
|
|
.AddMetaData<FTagMetaData>(TEXT("TimelineEditor.TickGroup"))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(6.f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextComboBox)
|
|
.OptionsSource(&TickGroupNameStrings)
|
|
.InitiallySelectedItem(TickGroupNameStrings[CurrentTickGroupNameStringIndex])
|
|
.OnSelectionChanged(this, &STimelineEditor::OnTimelineTickGroupChanged)
|
|
.ToolTipText(LOCTEXT("TimelineTickGroupDropdownTooltip", "Select the TickGroup you want this timeline to run in.\nTo assign options use context menu on timelines."))
|
|
]
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1)
|
|
[
|
|
// The list of tracks
|
|
SAssignNew( TrackListView, STimelineEdTrackListType )
|
|
.ListItemsSource( &TrackList )
|
|
.OnGenerateRow( this, &STimelineEditor::MakeTrackWidget )
|
|
.ItemHeight( 96 )
|
|
.OnItemScrolledIntoView(this, &STimelineEditor::OnItemScrolledIntoView)
|
|
.OnContextMenuOpening(this, &STimelineEditor::MakeContextMenu)
|
|
.SelectionMode(ESelectionMode::SingleToggle)
|
|
]
|
|
];
|
|
|
|
TimelineObj = InTimelineObj;
|
|
check(TimelineObj);
|
|
|
|
// Initial call to get list built
|
|
OnTimelineChanged();
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
void STimelineEditor::OnTimelineTickGroupChanged(TSharedPtr<FString> NewValue, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if (TickGroupNamesInitialized && TimelineObj && NewValue.IsValid())
|
|
{
|
|
if (const UEnum* TickGroupEnum = StaticEnum<ETickingGroup>())
|
|
{
|
|
ETickingGroup NewTickGroup = (ETickingGroup)TickGroupEnum->GetValueByNameString(*NewValue.Get());
|
|
if (NewTickGroup != TimelineObj->TimelineTickGroup)
|
|
{
|
|
TimelineObj->TimelineTickGroup = NewTickGroup;
|
|
|
|
// Mark blueprint as modified
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
if (UBlueprint* Blueprint = Kismet2->GetBlueprintObj())
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
FText STimelineEditor::GetTimelineName() const
|
|
{
|
|
if(TimelineObj != NULL)
|
|
{
|
|
return FText::FromString(TimelineObj->GetVariableName().ToString());
|
|
}
|
|
else
|
|
{
|
|
return LOCTEXT( "NoTimeline", "No Timeline" );
|
|
}
|
|
}
|
|
|
|
float STimelineEditor::GetViewMaxInput() const
|
|
{
|
|
return ViewMaxInput;
|
|
}
|
|
|
|
float STimelineEditor::GetViewMinInput() const
|
|
{
|
|
return ViewMinInput;
|
|
}
|
|
|
|
float STimelineEditor::GetViewMaxOutput() const
|
|
{
|
|
return ViewMaxOutput;
|
|
}
|
|
|
|
float STimelineEditor::GetViewMinOutput() const
|
|
{
|
|
return ViewMinOutput;
|
|
}
|
|
|
|
float STimelineEditor::GetTimelineLength() const
|
|
{
|
|
return (TimelineObj != NULL) ? TimelineObj->TimelineLength : 0.f;
|
|
}
|
|
|
|
void STimelineEditor::SetInputViewRange(float InViewMinInput, float InViewMaxInput)
|
|
{
|
|
ViewMaxInput = InViewMaxInput;
|
|
ViewMinInput = InViewMinInput;
|
|
}
|
|
|
|
void STimelineEditor::SetOutputViewRange(float InViewMinOutput, float InViewMaxOutput)
|
|
{
|
|
ViewMaxOutput = InViewMaxOutput;
|
|
ViewMinOutput = InViewMinOutput;
|
|
}
|
|
|
|
TSharedRef<ITableRow> STimelineEditor::MakeTrackWidget( TSharedPtr<FTimelineEdTrack> Track, const TSharedRef<STableViewBase>& OwnerTable )
|
|
{
|
|
check( Track.IsValid() );
|
|
|
|
return
|
|
SNew(STableRow< TSharedPtr<FTimelineEdTrack> >, OwnerTable )
|
|
.Style(&FAppStyle::Get().GetWidgetStyle<FTableRowStyle>("TimelineEditor.TrackRowSubtleHighlight"))
|
|
.Padding(FMargin(0, 0, 0, 2))
|
|
[
|
|
SNew(STimelineEdTrack, Track, SharedThis(this))
|
|
];
|
|
}
|
|
|
|
void STimelineEditor::CreateNewTrack(FTTTrackBase::ETrackType Type)
|
|
{
|
|
FName TrackName;
|
|
do
|
|
{
|
|
// MakeUniqueObjectName is misleading here since tracks aren't UObjects, although the function
|
|
// will still keep a counter for tracks. This may take a couple tries to find a valid name.
|
|
TrackName = MakeUniqueObjectName(TimelineObj, UTimelineTemplate::StaticClass(), FName(*(LOCTEXT("NewTrack_DefaultName", "NewTrack").ToString())));
|
|
} while (!TimelineObj->IsNewTrackNameValid(TrackName));
|
|
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
UClass* OwnerClass = Blueprint->GeneratedClass;
|
|
check(OwnerClass);
|
|
|
|
FText ErrorMessage;
|
|
|
|
if (TimelineNode)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "TimelineEditor_AddNewTrack", "Add new track" ) );
|
|
|
|
TimelineNode->Modify();
|
|
TimelineObj->Modify();
|
|
|
|
NewTrackPendingRename = TrackName;
|
|
|
|
FTTTrackId NewTrackId;
|
|
NewTrackId.TrackType = Type;
|
|
|
|
if(Type == FTTTrackBase::TT_Event)
|
|
{
|
|
NewTrackId.TrackIndex = TimelineObj->EventTracks.Num();
|
|
|
|
FTTEventTrack NewTrack;
|
|
NewTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewTrack.CurveKeys = NewObject<UCurveFloat>(OwnerClass, NAME_None, RF_Public); // Needs to be marked public so that it can be referenced from timeline instances in the level
|
|
NewTrack.CurveKeys->bIsEventCurve = true;
|
|
TimelineObj->EventTracks.Add(NewTrack);
|
|
}
|
|
else if(Type == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
NewTrackId.TrackIndex = TimelineObj->FloatTracks.Num();
|
|
|
|
FTTFloatTrack NewTrack;
|
|
NewTrack.SetTrackName(TrackName, TimelineObj);
|
|
// @hack for using existing curve assets. need something better!
|
|
NewTrack.CurveFloat = FindFirstObject<UCurveFloat>(*TrackName.ToString(), EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous);
|
|
if (NewTrack.CurveFloat == nullptr)
|
|
{
|
|
NewTrack.CurveFloat = NewObject<UCurveFloat>(OwnerClass, NAME_None, RF_Public);
|
|
}
|
|
TimelineObj->FloatTracks.Add(NewTrack);
|
|
}
|
|
else if(Type == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
NewTrackId.TrackIndex = TimelineObj->VectorTracks.Num();
|
|
|
|
FTTVectorTrack NewTrack;
|
|
NewTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewTrack.CurveVector = NewObject<UCurveVector>(OwnerClass, NAME_None, RF_Public);
|
|
TimelineObj->VectorTracks.Add(NewTrack);
|
|
}
|
|
else if(Type == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
NewTrackId.TrackIndex = TimelineObj->LinearColorTracks.Num();
|
|
|
|
FTTLinearColorTrack NewTrack;
|
|
NewTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewTrack.CurveLinearColor = NewObject<UCurveLinearColor>(OwnerClass, NAME_None, RF_Public);
|
|
TimelineObj->LinearColorTracks.Add(NewTrack);
|
|
}
|
|
|
|
TimelineObj->AddDisplayTrack(NewTrackId);
|
|
|
|
// Refresh the node that owns this timeline template to get new pin
|
|
TimelineNode->ReconstructNode();
|
|
Kismet2->RefreshEditors();
|
|
|
|
//rebuild the widgets!
|
|
OnTimelineChanged();
|
|
}
|
|
else
|
|
{
|
|
// invalid node for timeline
|
|
ErrorMessage = LOCTEXT( "InvalidTimelineNodeCreate","Failed to create track. Timeline node is invalid. Please remove timeline node." );
|
|
}
|
|
|
|
if (!ErrorMessage.IsEmpty())
|
|
{
|
|
FNotificationInfo Info(ErrorMessage);
|
|
Info.ExpireDuration = 3.0f;
|
|
Info.bUseLargeFont = false;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if ( Notification.IsValid() )
|
|
{
|
|
Notification->SetCompletionState( SNotificationItem::CS_Fail );
|
|
}
|
|
}
|
|
}
|
|
|
|
UCurveBase* STimelineEditor::CreateNewCurve(FTTTrackBase::ETrackType Type )
|
|
{
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
UClass* OwnerClass = Blueprint->GeneratedClass;
|
|
check(OwnerClass);
|
|
UCurveBase* NewCurve = NULL;
|
|
if(Type == FTTTrackBase::TT_Event)
|
|
{
|
|
NewCurve = NewObject<UCurveFloat>(OwnerClass, NAME_None, RF_Public);
|
|
}
|
|
else if(Type == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
NewCurve = NewObject<UCurveFloat>(OwnerClass, NAME_None, RF_Public);
|
|
}
|
|
else if(Type == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
NewCurve = NewObject<UCurveVector>(OwnerClass, NAME_None, RF_Public);
|
|
}
|
|
else if(Type == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
NewCurve = NewObject<UCurveLinearColor>(OwnerClass, NAME_None, RF_Public);
|
|
}
|
|
|
|
return NewCurve;
|
|
}
|
|
|
|
bool STimelineEditor::CanDeleteSelectedTracks() const
|
|
{
|
|
int32 SelectedItems = TrackListView->GetNumItemsSelected();
|
|
return (SelectedItems == 1);
|
|
}
|
|
|
|
void STimelineEditor::OnDeleteSelectedTracks()
|
|
{
|
|
if(TimelineObj != NULL)
|
|
{
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
|
|
TArray< TSharedPtr<FTimelineEdTrack> > SelTracks = TrackListView->GetSelectedItems();
|
|
if(SelTracks.Num() == 1)
|
|
{
|
|
if (TimelineNode)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "TimelineEditor_DeleteTrack", "Delete track" ) );
|
|
|
|
TimelineNode->Modify();
|
|
TimelineObj->Modify();
|
|
|
|
TSharedPtr<FTimelineEdTrack> SelTrack = SelTracks[0];
|
|
FTTTrackId TrackId = TimelineObj->GetDisplayTrackId(SelTrack->DisplayIndex);
|
|
FTTTrackBase::ETrackType TrackType = (FTTTrackBase::ETrackType)TrackId.TrackType;
|
|
|
|
TimelineObj->RemoveDisplayTrack(SelTrack->DisplayIndex);
|
|
|
|
if (TrackType == FTTTrackBase::TT_Event)
|
|
{
|
|
TimelineObj->EventTracks.RemoveAt(TrackId.TrackIndex);
|
|
}
|
|
else if (TrackType == FTTTrackBase::TT_FloatInterp)
|
|
{
|
|
TimelineObj->FloatTracks.RemoveAt(TrackId.TrackIndex);
|
|
}
|
|
else if (TrackType == FTTTrackBase::TT_VectorInterp)
|
|
{
|
|
TimelineObj->VectorTracks.RemoveAt(TrackId.TrackIndex);
|
|
}
|
|
else if (TrackType == FTTTrackBase::TT_LinearColorInterp)
|
|
{
|
|
TimelineObj->LinearColorTracks.RemoveAt(TrackId.TrackIndex);
|
|
}
|
|
|
|
// Refresh the node that owns this timeline template to remove pin
|
|
TimelineNode->ReconstructNode();
|
|
Kismet2->RefreshEditors();
|
|
|
|
//rebuild the widgets!
|
|
OnTimelineChanged();
|
|
TrackListView->RebuildList();
|
|
}
|
|
else
|
|
{
|
|
FNotificationInfo Info( LOCTEXT( "InvalidTimelineNodeDestroy","Failed to destroy track. Timeline node is invalid. Please remove timeline node." ) );
|
|
Info.ExpireDuration = 3.0f;
|
|
Info.bUseLargeFont = false;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if ( Notification.IsValid() )
|
|
{
|
|
Notification->SetCompletionState( SNotificationItem::CS_Fail );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UTimelineTemplate* STimelineEditor::GetTimeline()
|
|
{
|
|
return TimelineObj;
|
|
}
|
|
|
|
void STimelineEditor::OnTimelineChanged()
|
|
{
|
|
TrackList.Empty();
|
|
|
|
TSharedPtr<FTimelineEdTrack> NewlyCreatedTrack;
|
|
|
|
// If we have a timeline,
|
|
if(TimelineObj != NULL)
|
|
{
|
|
// Iterate over tracks and create entries in the array that drives the list widget
|
|
for (int32 i = 0; i < TimelineObj->GetNumDisplayTracks(); ++i)
|
|
{
|
|
FTTTrackId TrackId = TimelineObj->GetDisplayTrackId(i);
|
|
|
|
TSharedRef<FTimelineEdTrack> Track = FTimelineEdTrack::Make(i);
|
|
TrackList.Add(Track);
|
|
|
|
FTTTrackBase* TrackBase = TimelineEditorHelpers::GetTrackFromTimeline(TimelineObj, Track);
|
|
if (TrackBase->GetTrackName() == NewTrackPendingRename)
|
|
{
|
|
NewlyCreatedTrack = Track;
|
|
}
|
|
}
|
|
}
|
|
|
|
TrackListView->RequestListRefresh();
|
|
|
|
TrackListView->RequestScrollIntoView(NewlyCreatedTrack);
|
|
}
|
|
|
|
void STimelineEditor::OnItemScrolledIntoView( TSharedPtr<FTimelineEdTrack> InTrackNode, const TSharedPtr<ITableRow>& InWidget )
|
|
{
|
|
if(NewTrackPendingRename != NAME_None)
|
|
{
|
|
InTrackNode->OnRenameRequest.ExecuteIfBound();
|
|
NewTrackPendingRename = NAME_None;
|
|
}
|
|
}
|
|
|
|
ECheckBoxState STimelineEditor::IsAutoPlayChecked() const
|
|
{
|
|
return (TimelineObj && TimelineObj->bAutoPlay) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEditor::OnAutoPlayChanged(ECheckBoxState NewType)
|
|
{
|
|
if(TimelineObj)
|
|
{
|
|
TimelineObj->bAutoPlay = (NewType == ECheckBoxState::Checked) ? true : false;
|
|
|
|
// Refresh the node that owns this timeline template to cache play status
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
if (TimelineNode)
|
|
{
|
|
TimelineNode->bAutoPlay = TimelineObj->bAutoPlay;
|
|
|
|
// Mark blueprint as modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ECheckBoxState STimelineEditor::IsLoopChecked() const
|
|
{
|
|
return (TimelineObj && TimelineObj->bLoop) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEditor::OnLoopChanged(ECheckBoxState NewType)
|
|
{
|
|
if(TimelineObj)
|
|
{
|
|
TimelineObj->bLoop = (NewType == ECheckBoxState::Checked) ? true : false;
|
|
|
|
// Refresh the node that owns this timeline template to cache play status
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
if (TimelineNode)
|
|
{
|
|
TimelineNode->bLoop = TimelineObj->bLoop;
|
|
|
|
// Mark blueprint as modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
}
|
|
|
|
ECheckBoxState STimelineEditor::IsReplicatedChecked() const
|
|
{
|
|
return (TimelineObj && TimelineObj->bReplicated) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEditor::OnReplicatedChanged(ECheckBoxState NewType)
|
|
{
|
|
if(TimelineObj)
|
|
{
|
|
TimelineObj->bReplicated = (NewType == ECheckBoxState::Checked) ? true : false;
|
|
|
|
// Refresh the node that owns this timeline template to cache replicated status
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
if (TimelineNode)
|
|
{
|
|
TimelineNode->bReplicated = TimelineObj->bReplicated;
|
|
|
|
// Mark blueprint as modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
}
|
|
|
|
ECheckBoxState STimelineEditor::IsUseLastKeyframeChecked() const
|
|
{
|
|
return (TimelineObj && TimelineObj->LengthMode == ETimelineLengthMode::TL_LastKeyFrame) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEditor::OnUseLastKeyframeChanged(ECheckBoxState NewType)
|
|
{
|
|
if(TimelineObj)
|
|
{
|
|
TimelineObj->LengthMode = (NewType == ECheckBoxState::Checked) ? ETimelineLengthMode::TL_LastKeyFrame : ETimelineLengthMode::TL_TimelineLength;
|
|
|
|
// Mark blueprint as modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Kismet2Ptr.Pin()->GetBlueprintObj());
|
|
}
|
|
}
|
|
|
|
|
|
ECheckBoxState STimelineEditor::IsIgnoreTimeDilationChecked() const
|
|
{
|
|
return (TimelineObj && TimelineObj->bIgnoreTimeDilation) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void STimelineEditor::OnIgnoreTimeDilationChanged(ECheckBoxState NewType)
|
|
{
|
|
if (TimelineObj)
|
|
{
|
|
TimelineObj->bIgnoreTimeDilation = (NewType == ECheckBoxState::Checked) ? true : false;
|
|
|
|
// Refresh the node that owns this timeline template to cache play status
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
|
|
// Mark blueprint as modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
if (TimelineNode)
|
|
{
|
|
TimelineNode->bIgnoreTimeDilation = TimelineObj->bIgnoreTimeDilation;
|
|
}
|
|
}
|
|
}
|
|
|
|
FText STimelineEditor::GetLengthString() const
|
|
{
|
|
FString LengthString(TEXT("0.0"));
|
|
if(TimelineObj != NULL)
|
|
{
|
|
LengthString = FString::Printf(TEXT("%.2f"), TimelineObj->TimelineLength);
|
|
}
|
|
return FText::FromString(LengthString);
|
|
}
|
|
|
|
void STimelineEditor::OnLengthStringChanged(const FText& NewString, ETextCommit::Type CommitInfo)
|
|
{
|
|
bool bCommitted = (CommitInfo == ETextCommit::OnEnter) || (CommitInfo == ETextCommit::OnUserMovedFocus);
|
|
if(TimelineObj != NULL && bCommitted)
|
|
{
|
|
float NewLength = FCString::Atof( *NewString.ToString() );
|
|
if(NewLength > KINDA_SMALL_NUMBER)
|
|
{
|
|
TimelineObj->TimelineLength = NewLength;
|
|
|
|
// Mark blueprint as modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Kismet2Ptr.Pin()->GetBlueprintObj());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool STimelineEditor::OnVerifyTrackNameCommit(const FText& TrackName, FText& OutErrorMessage, FTTTrackBase* TrackBase, STimelineEdTrack* Track )
|
|
{
|
|
FName RequestedName( *TrackName.ToString() );
|
|
bool bValid(true);
|
|
|
|
if(TrackName.IsEmpty())
|
|
{
|
|
OutErrorMessage = LOCTEXT( "NameMissing_Error", "You must provide a name." );
|
|
bValid = false;
|
|
}
|
|
else if(TrackBase->GetTrackName() != RequestedName &&
|
|
false == TimelineObj->IsNewTrackNameValid(RequestedName))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("TrackName"), TrackName);
|
|
OutErrorMessage = FText::Format(LOCTEXT("AlreadyInUse", "\"{TrackName}\" is already in use."), Args);
|
|
bValid = false;
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
if (TimelineNode)
|
|
{
|
|
for(TArray<UEdGraphPin*>::TIterator PinIt(TimelineNode->Pins);PinIt;++PinIt)
|
|
{
|
|
UEdGraphPin* Pin = *PinIt;
|
|
|
|
if (Pin->PinName == RequestedName)
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("TrackName"), TrackName);
|
|
OutErrorMessage = FText::Format(LOCTEXT("PinAlreadyInUse", "\"{TrackName}\" is already in use as a default pin!"), Args);
|
|
bValid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
void STimelineEditor::OnTrackNameCommitted( const FText& StringName, ETextCommit::Type /*CommitInfo*/, FTTTrackBase* TrackBase, STimelineEdTrack* Track )
|
|
{
|
|
FName RequestedName( *StringName.ToString() );
|
|
if( TimelineObj->IsNewTrackNameValid(RequestedName))
|
|
{
|
|
TimelineObj->Modify();
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
|
|
if (TimelineNode)
|
|
{
|
|
// Start looking from the bottom of the list of pins, where user defined ones are stored.
|
|
// It should not be possible to name pins to be the same as default pins,
|
|
// but in the case (fixes broken nodes) that they happen to be the same, this protects them
|
|
for (int32 PinIdx = TimelineNode->Pins.Num() - 1; PinIdx >= 0; --PinIdx)
|
|
{
|
|
UEdGraphPin* Pin = TimelineNode->Pins[PinIdx];
|
|
|
|
if (Pin->PinName == TrackBase->GetTrackName())
|
|
{
|
|
Pin->Modify();
|
|
Pin->PinName = RequestedName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
TrackBase->SetTrackName(RequestedName, TimelineObj);
|
|
|
|
Kismet2->RefreshEditors();
|
|
OnTimelineChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void STimelineEditor::OnReorderTracks(int32 DisplayIndex, int32 DirectionDelta)
|
|
{
|
|
if (TimelineObj != NULL)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("TimelineEditor_DeleteTrack", "Delete track"));
|
|
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
|
|
TimelineNode->Modify();
|
|
TimelineObj->Modify();
|
|
|
|
TimelineObj->MoveDisplayTrack(DisplayIndex, DirectionDelta);
|
|
|
|
// Refresh the node that owns this timeline template to remove pin
|
|
TimelineNode->ReconstructNode();
|
|
Kismet2->RefreshEditors();
|
|
}
|
|
}
|
|
|
|
bool STimelineEditor::IsCurveAssetSelected() const
|
|
{
|
|
// Note: Cannot call GetContentBrowserSelectionClasses() during serialization and GC due to its use of FindObject()
|
|
if(!GIsSavingPackage && !IsGarbageCollecting())
|
|
{
|
|
TArray<UClass*> SelectionList;
|
|
GEditor->GetContentBrowserSelectionClasses(SelectionList);
|
|
|
|
for( int i=0; i<SelectionList.Num(); i++ )
|
|
{
|
|
UClass* Item = SelectionList[i];
|
|
if( Item->IsChildOf(UCurveBase::StaticClass()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void STimelineEditor::CreateNewTrackFromAsset()
|
|
{
|
|
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
|
|
UCurveBase* SelectedObj = GEditor->GetSelectedObjects()->GetTop<UCurveBase>();
|
|
|
|
TSharedPtr<FBlueprintEditor> Kismet2 = Kismet2Ptr.Pin();
|
|
UBlueprint* Blueprint = Kismet2->GetBlueprintObj();
|
|
UK2Node_Timeline* TimelineNode = FBlueprintEditorUtils::FindNodeForTimeline(Blueprint, TimelineObj);
|
|
|
|
if( SelectedObj && TimelineNode )
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT( "TimelineEditor_CreateFromAsset", "Add new track from asset" ) );
|
|
|
|
TimelineNode->Modify();
|
|
TimelineObj->Modify();
|
|
|
|
const FName TrackName = SelectedObj->GetFName();
|
|
|
|
if(SelectedObj->IsA( UCurveFloat::StaticClass() ) )
|
|
{
|
|
UCurveFloat* FloatCurveObj = CastChecked<UCurveFloat>(SelectedObj);
|
|
if( FloatCurveObj->bIsEventCurve )
|
|
{
|
|
FTTEventTrack NewEventTrack;
|
|
NewEventTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewEventTrack.CurveKeys = CastChecked<UCurveFloat>(SelectedObj);
|
|
NewEventTrack.bIsExternalCurve = true;
|
|
|
|
TimelineObj->EventTracks.Add(NewEventTrack);
|
|
}
|
|
else
|
|
{
|
|
FTTFloatTrack NewFloatTrack;
|
|
NewFloatTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewFloatTrack.CurveFloat = CastChecked<UCurveFloat>(SelectedObj);
|
|
NewFloatTrack.bIsExternalCurve = true;
|
|
|
|
TimelineObj->FloatTracks.Add(NewFloatTrack);
|
|
}
|
|
}
|
|
else if(SelectedObj->IsA( UCurveVector::StaticClass() ))
|
|
{
|
|
FTTVectorTrack NewTrack;
|
|
NewTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewTrack.CurveVector = CastChecked<UCurveVector>(SelectedObj);
|
|
NewTrack.bIsExternalCurve = true;
|
|
TimelineObj->VectorTracks.Add(NewTrack);
|
|
}
|
|
else if(SelectedObj->IsA( UCurveLinearColor::StaticClass() ))
|
|
{
|
|
FTTLinearColorTrack NewTrack;
|
|
NewTrack.SetTrackName(TrackName, TimelineObj);
|
|
NewTrack.CurveLinearColor = CastChecked<UCurveLinearColor>(SelectedObj);
|
|
NewTrack.bIsExternalCurve = true;
|
|
TimelineObj->LinearColorTracks.Add(NewTrack);
|
|
}
|
|
|
|
// Refresh the node that owns this timeline template to get new pin
|
|
TimelineNode->ReconstructNode();
|
|
Kismet2->RefreshEditors();
|
|
}
|
|
}
|
|
|
|
bool STimelineEditor::CanRenameSelectedTrack() const
|
|
{
|
|
return TrackListView->GetNumItemsSelected() == 1;
|
|
}
|
|
|
|
void STimelineEditor::OnRequestTrackRename() const
|
|
{
|
|
check(TrackListView->GetNumItemsSelected() == 1);
|
|
|
|
TrackListView->GetSelectedItems()[0]->OnRenameRequest.Execute();
|
|
}
|
|
|
|
FReply STimelineEditor::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
if(CommandList->ProcessCommandBindings(InKeyEvent))
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
TSharedPtr< SWidget > STimelineEditor::MakeContextMenu() const
|
|
{
|
|
// Build up the menu
|
|
FMenuBuilder MenuBuilder( true, CommandList );
|
|
{
|
|
MenuBuilder.AddMenuEntry( FGenericCommands::Get().Rename );
|
|
MenuBuilder.AddMenuEntry( FGenericCommands::Get().Delete );
|
|
}
|
|
|
|
{
|
|
TSharedRef<SWidget> SizeSlider = SNew(SSlider)
|
|
.Value(this, &STimelineEditor::GetSizeScaleValue)
|
|
.OnValueChanged(const_cast<STimelineEditor*>(this), &STimelineEditor::SetSizeScaleValue);
|
|
|
|
MenuBuilder.AddWidget(SizeSlider, LOCTEXT("TimelineEditorVerticalSize", "Height"));
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
|
|
TSharedRef<SWidget> STimelineEditor::MakeAddButton()
|
|
{
|
|
FMenuBuilder MenuBuilder(true, nullptr);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AddFloatTrack", "Add Float Track"),
|
|
LOCTEXT("AddFloatTrackToolTip", "Adds a Float Track."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "TimelineEditor.AddFloatTrack"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &STimelineEditor::CreateNewTrack, FTTTrackBase::TT_FloatInterp)));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AddVectorTrack", "Add Vector Track"),
|
|
LOCTEXT("AddVectorTrackToolTip", "Adds a Vector Track."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "TimelineEditor.AddVectorTrack"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &STimelineEditor::CreateNewTrack, FTTTrackBase::TT_VectorInterp)));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AddEventTrack", "Add Event Track"),
|
|
LOCTEXT("AddEventTrackToolTip", "Adds an Event Track."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "TimelineEditor.AddEventTrack"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &STimelineEditor::CreateNewTrack, FTTTrackBase::TT_Event)));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AddColorTrack", "Add Color Track"),
|
|
LOCTEXT("AddColorTrackToolTip", "Adds a Color Track."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "TimelineEditor.AddColorTrack"),
|
|
FUIAction(FExecuteAction::CreateRaw(this, &STimelineEditor::CreateNewTrack, FTTTrackBase::TT_LinearColorInterp)));
|
|
|
|
FUIAction AddCurveAssetAction(FExecuteAction::CreateRaw(this, &STimelineEditor::CreateNewTrackFromAsset), FCanExecuteAction::CreateRaw(this, &STimelineEditor::IsCurveAssetSelected));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("AddExternalAsset", "Add Selected Curve Asset"),
|
|
LOCTEXT("AddExternalAssetToolTip", "Add the currently selected curve asset."),
|
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "TimelineEditor.AddCurveAssetTrack"),
|
|
AddCurveAssetAction);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
FVector2D STimelineEditor::GetTimelineDesiredSize() const
|
|
{
|
|
return TimelineDesiredSize;
|
|
}
|
|
|
|
void STimelineEditor::SetSizeScaleValue(float NewValue)
|
|
{
|
|
TimelineDesiredSize.Y = NominalTimelineDesiredHeight * (1.0f + NewValue * 5.0f);
|
|
TrackListView->RequestListRefresh();
|
|
}
|
|
|
|
float STimelineEditor::GetSizeScaleValue() const
|
|
{
|
|
return ((TimelineDesiredSize.Y / NominalTimelineDesiredHeight) - 1.0f) / 5.0f;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|