Files
UnrealEngine/Engine/Source/Editor/MovieSceneTools/Private/Channels/BuiltInChannelEditors.cpp
2025-05-18 13:04:45 +08:00

2290 lines
87 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Channels/BuiltInChannelEditors.h"
#include "MovieSceneSequence.h"
#include "MovieSceneSequenceEditor.h"
#include "MovieSceneEventUtils.h"
#include "Channels/MovieSceneChannelHandle.h"
#include "Sections/MovieSceneEventSectionBase.h"
#include "ISequencerChannelInterface.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/SNullWidget.h"
#include "IKeyArea.h"
#include "ISequencer.h"
#include "SequencerSettings.h"
#include "CurveEditor.h"
#include "MovieSceneCommonHelpers.h"
#include "GameFramework/Actor.h"
#include "Styling/AppStyle.h"
#include "Styling/CoreStyle.h"
#include "CurveKeyEditors/SNumericKeyEditor.h"
#include "CurveKeyEditors/SBoolCurveKeyEditor.h"
#include "CurveKeyEditors/SStringCurveKeyEditor.h"
#include "CurveKeyEditors/SEnumKeyEditor.h"
#include "UObject/StructOnScope.h"
#include "MVVM/Views/KeyDrawParams.h"
#include "MVVM/ViewModels/TimeWarpChannelModel.h"
#include "MVVM/ViewModels/SectionModel.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "Channels/MovieSceneChannelEditorData.h"
#include "Channels/MovieSceneFloatChannel.h"
#include "Channels/MovieSceneDoubleChannel.h"
#include "Channels/MovieSceneBoolChannel.h"
#include "Channels/MovieSceneByteChannel.h"
#include "Channels/MovieSceneIntegerChannel.h"
#include "Channels/DoubleChannelCurveModel.h"
#include "Channels/FloatChannelCurveModel.h"
#include "Channels/IntegerChannelCurveModel.h"
#include "Channels/BoolChannelCurveModel.h"
#include "Channels/ByteChannelCurveModel.h"
#include "Channels/TimeWarpChannelCurveModel.h"
#include "Channels/PiecewiseCurveModel.h"
#include "Variants/MovieScenePlayRateCurve.h"
#include "EventChannelCurveModel.h"
#include "InvertedCurveModel.h"
#include "PropertyCustomizationHelpers.h"
#include "MovieSceneObjectBindingIDCustomization.h"
#include "MovieSceneObjectBindingIDPicker.h"
#include "LevelEditor.h"
#include "Modules/ModuleManager.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "SSocketChooser.h"
#include "SComponentChooser.h"
#include "EntitySystem/MovieSceneDecompositionQuery.h"
#include "EntitySystem/Interrogation/MovieSceneInterrogationLinker.h"
#include "EntitySystem/Interrogation/MovieSceneInterrogatedPropertyInstantiator.h"
#include "Systems/MovieScenePropertyInstantiator.h"
#include "Tracks/MovieSceneObjectPropertyTrack.h"
#include "Tracks/MovieScenePropertyTrack.h"
#include "Widgets/Input/SComboButton.h"
#include "MovieSceneSpawnableAnnotation.h"
#include "ISequencerModule.h"
#include "MovieScene.h"
#include "MovieSceneTracksComponentTypes.h"
#define LOCTEXT_NAMESPACE "BuiltInChannelEditors"
template<typename ChannelType, typename ValueType>
FKeyHandle AddOrUpdateKeyImpl(ChannelType* Channel, UMovieSceneSection* SectionToKey, const TMovieSceneExternalValue<ValueType>& ExternalValue, FFrameNumber InTime, ISequencer& Sequencer, const FGuid& InObjectBindingID, FTrackInstancePropertyBindings* PropertyBindings)
{
using namespace UE::MovieScene;
const FMovieSceneSequenceID SequenceID = Sequencer.GetFocusedTemplateID();
// Find the first bound object so we can get the current property channel value on it.
UObject* FirstBoundObject = nullptr;
TOptional<ValueType> CurrentBoundObjectValue;
if (InObjectBindingID.IsValid())
{
for (TWeakObjectPtr<> WeakObject : Sequencer.FindBoundObjects(InObjectBindingID, SequenceID))
{
if (UObject* Object = WeakObject.Get())
{
FirstBoundObject = Object;
if (ExternalValue.OnGetExternalValue)
{
CurrentBoundObjectValue = ExternalValue.OnGetExternalValue(*Object, PropertyBindings);
}
break;
}
}
}
// If we got the current property channel value on the object, let's get the current evaluated property channel value at the given time (which is the value that the
// object *would* be at if we scrubbed here and let the sequence evaluation do its thing). This will help us figure out the difference between the current object value
// and the evaluated sequencer value: we will compute a new value for the channel so that a new sequence evaluation would come out at the "desired" value, which is
// what the current object value.
ValueType NewValue = Channel->GetDefault().Get(0.f);
const bool bWasEvaluated = Channel->Evaluate(InTime, NewValue);
if (CurrentBoundObjectValue.IsSet() && SectionToKey)
{
if (ExternalValue.OnGetCurrentValueAndWeight)
{
// We have a custom callback that can provide us with the evaluated value of this channel.
ValueType CurrentValue = CurrentBoundObjectValue.Get(0.0f);
float CurrentWeight = 1.0f;
FMovieSceneRootEvaluationTemplateInstance& EvaluationTemplate = Sequencer.GetEvaluationTemplate();
ExternalValue.OnGetCurrentValueAndWeight(FirstBoundObject, SectionToKey, InTime, Sequencer.GetFocusedTickResolution(), EvaluationTemplate, CurrentValue, CurrentWeight);
if (CurrentBoundObjectValue.IsSet()) //need to get the diff between Value(Global) and CurrentValue and apply that to the local
{
if (bWasEvaluated)
{
ValueType CurrentGlobalValue = CurrentBoundObjectValue.GetValue();
NewValue = (CurrentBoundObjectValue.Get(0.0f) - CurrentValue) * CurrentWeight + NewValue;
}
else //Nothing set (key or default) on channel so use external value
{
NewValue = CurrentBoundObjectValue.Get(0.0f);
}
}
}
else
{
// No custom callback... we need to run the blender system on our property.
FSystemInterrogator Interrogator;
Interrogator.TrackImportedEntities(true);
TGuardValue<FEntityManager*> DebugVizGuard(GEntityManagerForDebuggingVisualizers, &Interrogator.GetLinker()->EntityManager);
UMovieSceneTrack* TrackToKey = SectionToKey->GetTypedOuter<UMovieSceneTrack>();
// If we are keying something for a property track, give the interrogator all the info it needs
// to know about the bound object. This will let it, for instance, cache the correct initial values
// for that property.
FInterrogationKey InterrogationKey(FInterrogationKey::Default());
UMovieScenePropertyTrack* PropertyTrackToKey = Cast<UMovieScenePropertyTrack>(TrackToKey);
if (PropertyTrackToKey != nullptr)
{
const FInterrogationChannel InterrogationChannel = Interrogator.AllocateChannel(FirstBoundObject, PropertyTrackToKey->GetPropertyBinding());
InterrogationKey.Channel = InterrogationChannel;
Interrogator.ImportTrack(TrackToKey, InterrogationChannel);
}
else
{
Interrogator.ImportTrack(TrackToKey, FInterrogationChannel::Default());
}
// Interrogate!
Interrogator.AddInterrogation(InTime);
Interrogator.Update();
const FMovieSceneEntityID EntityID = Interrogator.FindEntityFromOwner(InterrogationKey, SectionToKey, 0);
UMovieSceneInterrogatedPropertyInstantiatorSystem* System = Interrogator.GetLinker()->FindSystem<UMovieSceneInterrogatedPropertyInstantiatorSystem>();
if (ensure(System) && EntityID) // EntityID can be invalid here if we are keying a section that is currently empty
{
const FMovieSceneChannelProxy& SectionChannelProxy = SectionToKey->GetChannelProxy();
const FName ChannelTypeName = Channel->StaticStruct()->GetFName();
int32 ChannelIndex = SectionChannelProxy.FindIndex(ChannelTypeName, Channel);
FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
// Find the property definition based on the property tag that our section entity has.
int32 BoundPropertyDefinitionIndex = INDEX_NONE;
TArrayView<const FPropertyDefinition> PropertyDefinitions = BuiltInComponents->PropertyRegistry.GetProperties();
for (int32 Index = 0; Index < PropertyDefinitions.Num(); ++Index)
{
const FPropertyDefinition& PropertyDefinition = PropertyDefinitions[Index];
if (Interrogator.GetLinker()->EntityManager.HasComponent(EntityID, PropertyDefinition.PropertyType))
{
BoundPropertyDefinitionIndex = Index;
break;
}
}
if (ensure(ChannelIndex != INDEX_NONE && BoundPropertyDefinitionIndex != INDEX_NONE))
{
const FPropertyDefinition& BoundPropertyDefinition = PropertyDefinitions[BoundPropertyDefinitionIndex];
check(FirstBoundObject != nullptr);
TComponentLock<TReadOptional<FBoundObjectResolver>> Resolver =
Interrogator.GetLinker()->EntityManager.ReadComponent(EntityID, BuiltInComponents->BoundObjectResolver);
if (Resolver)
{
FirstBoundObject = (*Resolver)(FirstBoundObject);
check(FirstBoundObject != nullptr);
}
FDecompositionQuery Query;
Query.Entities = MakeArrayView(&EntityID, 1);
Query.bConvertFromSourceEntityIDs = false;
Query.Object = FirstBoundObject;
FIntermediate3DTransform InTransformData;
TRecompositionResult<double> RecomposeResult = System->RecomposeBlendChannel(BoundPropertyDefinition, ChannelIndex, Query, (double)CurrentBoundObjectValue.Get(0.f));
NewValue = (ValueType)RecomposeResult.Values[0];
}
}
}
}
using namespace UE::MovieScene;
EMovieSceneKeyInterpolation KeyInterpolation = GetInterpolationMode(Channel,InTime,Sequencer.GetKeyInterpolation());
return AddKeyToChannel(Channel, InTime, NewValue, KeyInterpolation);
}
FKeyHandle AddOrUpdateKey(FMovieSceneFloatChannel* Channel, UMovieSceneSection* SectionToKey, const TMovieSceneExternalValue<float>& ExternalValue, FFrameNumber InTime, ISequencer& Sequencer, const FGuid& InObjectBindingID, FTrackInstancePropertyBindings* PropertyBindings)
{
return AddOrUpdateKeyImpl<FMovieSceneFloatChannel, float>(Channel, SectionToKey, ExternalValue, InTime, Sequencer, InObjectBindingID, PropertyBindings);
}
FKeyHandle AddOrUpdateKey(FMovieSceneDoubleChannel* Channel, UMovieSceneSection* SectionToKey, const TMovieSceneExternalValue<double>& ExternalValue, FFrameNumber InTime, ISequencer& Sequencer, const FGuid& InObjectBindingID, FTrackInstancePropertyBindings* PropertyBindings)
{
return AddOrUpdateKeyImpl<FMovieSceneDoubleChannel, double>(Channel, SectionToKey, ExternalValue, InTime, Sequencer, InObjectBindingID, PropertyBindings);
}
FKeyHandle AddOrUpdateKey(FMovieSceneTimeWarpChannel* Channel, UMovieSceneSection* SectionToKey, FFrameNumber InTime, ISequencer& Sequencer, const FGuid& InObjectBindingID, FTrackInstancePropertyBindings* PropertyBindings)
{
TMovieSceneExternalValue<double> ExternalValue;
return AddOrUpdateKey(Channel, SectionToKey, ExternalValue, InTime, Sequencer, InObjectBindingID, PropertyBindings);
}
FKeyHandle AddOrUpdateKey(FMovieSceneActorReferenceData* Channel, UMovieSceneSection* SectionToKey, FFrameNumber InTime, ISequencer& Sequencer, const FGuid& InObjectBindingID, FTrackInstancePropertyBindings* PropertyBindings)
{
if (PropertyBindings && InObjectBindingID.IsValid())
{
for (TWeakObjectPtr<> WeakObject : Sequencer.FindBoundObjects(InObjectBindingID, Sequencer.GetFocusedTemplateID()))
{
if (UObject* Object = WeakObject.Get())
{
// Care is taken here to ensure that we call GetCurrentValue with the correct instantiation of UObject* rather than AActor*
AActor* CurrentActor = Cast<AActor>(PropertyBindings->GetCurrentValue<UObject*>(*Object));
if (CurrentActor)
{
FMovieSceneObjectBindingID Binding;
TOptional<FMovieSceneSpawnableAnnotation> Spawnable = FMovieSceneSpawnableAnnotation::Find(CurrentActor);
if (Spawnable.IsSet())
{
// Check whether the spawnable is underneath the current sequence, if so, we can remap it to a local sequence ID
Binding = UE::MovieScene::FRelativeObjectBindingID(Sequencer.GetFocusedTemplateID(), Spawnable->SequenceID, Spawnable->ObjectBindingID, Sequencer);
}
else
{
FGuid ThisGuid = Sequencer.GetHandleToObject(CurrentActor);
Binding = UE::MovieScene::FRelativeObjectBindingID(ThisGuid);
}
int32 NewIndex = Channel->GetData().AddKey(InTime, Binding);
return Channel->GetData().GetHandle(NewIndex);
}
}
}
}
FMovieSceneActorReferenceKey NewValue;
Channel->Evaluate(InTime, NewValue);
return Channel->GetData().UpdateOrAddKey(InTime, NewValue);
}
bool CanCreateKeyEditor(const FMovieSceneBoolChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneByteChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneIntegerChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneFloatChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneDoubleChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneStringChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneObjectPathChannel* Channel)
{
return true;
}
bool CanCreateKeyEditor(const FMovieSceneActorReferenceData* Channel)
{
return true;
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneBoolChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const TMovieSceneExternalValue<bool>* ExternalValue = Channel.GetExtendedEditorData();
if (!ExternalValue)
{
return SNullWidget::NullWidget;
}
TSequencerKeyEditor<FMovieSceneBoolChannel, bool> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue->OnGetExternalValue
);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
return SNew(SBoolCurveKeyEditor, KeyEditor);
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneIntegerChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const TMovieSceneExternalValue<int32>* ExternalValue = Channel.GetExtendedEditorData();
if (!ExternalValue)
{
return SNullWidget::NullWidget;
}
TSequencerKeyEditor<FMovieSceneIntegerChannel, int32> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue->OnGetExternalValue
);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
typedef SNumericKeyEditor<FMovieSceneIntegerChannel, int32> KeyEditorType;
return SNew(KeyEditorType, KeyEditor);
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneFloatChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const TMovieSceneExternalValue<float>* ExternalValue = Channel.GetExtendedEditorData();
if (!ExternalValue)
{
return SNullWidget::NullWidget;
}
TSequencerKeyEditor<FMovieSceneFloatChannel, float> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue->OnGetExternalValue
);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
typedef SNumericKeyEditor<FMovieSceneFloatChannel, float> KeyEditorType;
return SNew(KeyEditorType, KeyEditor);
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneDoubleChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
TFunction<TOptional<double>(UObject&, FTrackInstancePropertyBindings*)> ExternalValue;
if (const TMovieSceneExternalValue<double>* ExternalValuePtr = Channel.GetExtendedEditorData())
{
ExternalValue = ExternalValuePtr->OnGetExternalValue;
}
TSequencerKeyEditor<FMovieSceneDoubleChannel, double> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue
);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
typedef SNumericKeyEditor<FMovieSceneDoubleChannel, double> KeyEditorType;
return SNew(KeyEditorType, KeyEditor);
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneStringChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const TMovieSceneExternalValue<FString>* ExternalValue = Channel.GetExtendedEditorData();
if (!ExternalValue)
{
return SNullWidget::NullWidget;
}
TSequencerKeyEditor<FMovieSceneStringChannel, FString> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue->OnGetExternalValue
);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
return SNew(SStringCurveKeyEditor, KeyEditor);
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneByteChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const TMovieSceneExternalValue<uint8>* ExternalValue = Channel.GetExtendedEditorData();
const FMovieSceneByteChannel* RawChannel = Channel.Get();
if (!ExternalValue || !RawChannel)
{
return SNullWidget::NullWidget;
}
TSequencerKeyEditor<FMovieSceneByteChannel, uint8> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue->OnGetExternalValue
);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
if (UEnum* Enum = RawChannel->GetEnum())
{
return SNew(SEnumCurveKeyEditor, KeyEditor, Enum);
}
else
{
typedef SNumericKeyEditor<FMovieSceneByteChannel, uint8> KeyEditorType;
return SNew(KeyEditorType, KeyEditor);
}
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneObjectPathChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const TMovieSceneExternalValue<UObject*>* ExternalValue = Channel.GetExtendedEditorData();
const FMovieSceneObjectPathChannel* RawChannel = Channel.Get();
UMovieSceneObjectPropertyTrack* ObjectPathTrack = Cast<UMovieSceneObjectPropertyTrack>(Params.OwningSection->GetOuter());
if (ExternalValue && RawChannel)
{
TSequencerKeyEditor<FMovieSceneObjectPathChannel, UObject*> KeyEditor(Params.ObjectBindingID, Channel, Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue->OnGetExternalValue);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
UClass* PropertyClass = ObjectPathTrack ? ObjectPathTrack->PropertyClass : nullptr;
const bool bClassPicker = ObjectPathTrack ? ObjectPathTrack->bClassProperty : false;
if (bClassPicker)
{
auto OnSetClassLambda = [KeyEditor](const UClass* Class) mutable
{
FScopedTransaction Transaction(LOCTEXT("SetObjectPathKey", "Set Object Path Key Value"));
KeyEditor.SetValueWithNotify(const_cast<UClass*>(Class), EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
};
auto GetSelectedClassLambda = [KeyEditor]() -> const UClass*
{
return Cast<UClass>(KeyEditor.GetCurrentValue());
};
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(100.f)
[
SNew(SClassPropertyEntryBox)
.MetaClass(PropertyClass)
.SelectedClass_Lambda(GetSelectedClassLambda)
.OnSetClass_Lambda(OnSetClassLambda)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(SSpacer)
];
}
else
{
auto OnSetObjectLambda = [KeyEditor](const FAssetData& Asset) mutable
{
FScopedTransaction Transaction(LOCTEXT("SetObjectPathKey", "Set Object Path Key Value"));
KeyEditor.SetValueWithNotify(Asset.GetAsset(), EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
};
auto GetObjectPathLambda = [KeyEditor]() -> FString
{
UObject* Obj = KeyEditor.GetCurrentValue();
return Obj ? Obj->GetPathName() : FString();
};
TArray<FAssetData> AssetDataArray;
UMovieSceneSequence* Sequence = Params.Sequencer->GetFocusedMovieSceneSequence();
AssetDataArray.Add((FAssetData)Sequence);
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(100.f)
[
SNew(SObjectPropertyEntryBox)
.DisplayBrowse(true)
.DisplayUseSelected(false)
.ObjectPath_Lambda(GetObjectPathLambda)
.AllowedClass(RawChannel->GetPropertyClass())
.OnObjectChanged_Lambda(OnSetObjectLambda)
.OwnerAssetDataArray(AssetDataArray)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(8.0f, 0.0f)
[
SNew(SSpacer)
];
}
}
return SNullWidget::NullWidget;
}
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneTimeWarpChannel>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
TFunction<TOptional<double>(UObject&, FTrackInstancePropertyBindings*)> ExternalValue;
TSequencerKeyEditor<FMovieSceneTimeWarpChannel, double> KeyEditor(
Params.ObjectBindingID, Channel,
Params.OwningSection, Params.Sequencer, Params.PropertyBindings, ExternalValue
);
KeyEditor.SetApplyInUnwarpedLocalSpace(true);
KeyEditor.SetOwningObject(Cast<UMovieSceneSignedObject>(Params.OwningObject));
const FMovieSceneTimeWarpChannel* ChannelPtr = Channel.Get();
if (ChannelPtr && ChannelPtr->Domain == UE::MovieScene::ETimeWarpChannelDomain::Time)
{
// Set the numeric type interface for frame numbers if the channel is in the time domain
KeyEditor.SetNumericTypeInterface(Params.Sequencer->GetNumericTypeInterface());
}
typedef SNumericKeyEditor<FMovieSceneTimeWarpChannel, double> KeyEditorType;
return SNew(KeyEditorType, KeyEditor);
}
template<typename ChannelType>
void GetTypedChannels(ISequencer* Sequencer, const TSet<TWeakObjectPtr<UMovieSceneSection>>& WeakSections, TArray<ChannelType*>& Channels)
{
// Get selected channels
TArray<const IKeyArea*> KeyAreas;
Sequencer->GetSelectedKeyAreas(KeyAreas);
for (const IKeyArea* KeyArea : KeyAreas)
{
FMovieSceneChannelHandle Handle = KeyArea->GetChannel();
if (Handle.GetChannelTypeName() == ChannelType::StaticStruct()->GetFName())
{
ChannelType* Channel = static_cast<ChannelType*>(Handle.Get());
Channels.Add(Channel);
}
}
// Otherwise, the channels of all the sections
if (Channels.Num() == 0)
{
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (ChannelType* Channel : ChannelProxy.GetChannels<ChannelType>())
{
Channels.Add(Channel);
}
}
}
}
}
/** Delegate used to set a class */
DECLARE_DELEGATE_OneParam(FOnSetActorReferenceKey, FMovieSceneActorReferenceKey);
class SActorReferenceBox : public SCompoundWidget, public FMovieSceneObjectBindingIDPicker
{
public:
SLATE_BEGIN_ARGS(SActorReferenceBox)
{}
SLATE_ATTRIBUTE(FMovieSceneActorReferenceKey, ActorReferenceKey)
SLATE_EVENT(FOnSetActorReferenceKey, OnSetActorReferenceKey)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, TWeakPtr<ISequencer> InSequencer)
{
WeakSequencer = InSequencer;
LocalSequenceID = InSequencer.Pin()->GetFocusedTemplateID();
Key = InArgs._ActorReferenceKey;
SetKey = InArgs._OnSetActorReferenceKey;
OnGlobalTimeChangedHandle = WeakSequencer.Pin()->OnGlobalTimeChanged().AddRaw(this, &SActorReferenceBox::GlobalTimeChanged);
OnMovieSceneDataChangedHandle = WeakSequencer.Pin()->OnMovieSceneDataChanged().AddRaw(this, &SActorReferenceBox::MovieSceneDataChanged);
ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(100.f)
[
SNew(SComboButton)
.OnGetMenuContent(this, &SActorReferenceBox::GetPickerMenu)
.ContentPadding(FMargin(0.0, 0.0))
.ButtonStyle(FAppStyle::Get(), "PropertyEditor.AssetComboStyle")
.ForegroundColor(FAppStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity"))
.ButtonContent()
[
GetCurrentItemWidget(
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "PropertyEditor.AssetClass")
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 8))
)
]
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(4.f, 0.f, 0.f, 0.f))
[
GetWarningWidget()
]
];
Update();
}
virtual ~SActorReferenceBox()
{
if (WeakSequencer.IsValid())
{
WeakSequencer.Pin()->OnGlobalTimeChanged().Remove(OnGlobalTimeChangedHandle);
WeakSequencer.Pin()->OnMovieSceneDataChanged().Remove(OnMovieSceneDataChangedHandle);
}
}
virtual UMovieSceneSequence* GetSequence() const override
{
return WeakSequencer.Pin()->GetFocusedMovieSceneSequence();
}
/** Set the current binding ID */
virtual void SetCurrentValue(const FMovieSceneObjectBindingID& InBindingId) override
{
SetKey.Execute(FMovieSceneActorReferenceKey(InBindingId));
}
/** Get the current binding ID */
virtual FMovieSceneObjectBindingID GetCurrentValue() const override
{
return Key.Get().Object;
}
void GlobalTimeChanged()
{
Update();
}
void MovieSceneDataChanged(EMovieSceneDataChangeType)
{
Update();
}
void Update()
{
if (IsEmpty())
{
Initialize();
}
else
{
UpdateCachedData();
}
}
private:
TAttribute< FMovieSceneActorReferenceKey> Key;
FOnSetActorReferenceKey SetKey;
FDelegateHandle OnGlobalTimeChangedHandle, OnMovieSceneDataChangedHandle;
};
TSharedRef<SWidget> CreateKeyEditor(const TMovieSceneChannelHandle<FMovieSceneActorReferenceData>& Channel, const UE::Sequencer::FCreateKeyEditorParams& Params)
{
const FMovieSceneActorReferenceData* RawChannel = Channel.Get();
if (!RawChannel)
{
return SNullWidget::NullWidget;
}
TFunction<TOptional<FMovieSceneActorReferenceKey>(UObject&, FTrackInstancePropertyBindings*)> Func;
TSequencerKeyEditor<FMovieSceneActorReferenceData, FMovieSceneActorReferenceKey> KeyEditor(Params.ObjectBindingID, Channel, Params.OwningSection, Params.Sequencer, Params.PropertyBindings, Func);
auto OnSetCurrentValueLambda = [KeyEditor](const FMovieSceneActorReferenceKey& ActorKey) mutable
{
FScopedTransaction Transaction(LOCTEXT("SetActorReferenceKey", "Set Actor Reference Key Value"));
KeyEditor.SetValueWithNotify(ActorKey, EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
// Look for components to choose
ISequencer* Sequencer = KeyEditor.GetSequencer();
TArray<USceneComponent*> ComponentsWithSockets;
AActor* Actor = nullptr;
for (TWeakObjectPtr<> WeakObject : ActorKey.Object.ResolveBoundObjects(Sequencer->GetFocusedTemplateID(), *Sequencer))
{
Actor = Cast<AActor>(WeakObject.Get());
if (Actor)
{
TInlineComponentArray<USceneComponent*> Components(Actor);
for (USceneComponent* Component : Components)
{
if (Component && Component->HasAnySockets())
{
ComponentsWithSockets.Add(Component);
}
}
break;
}
}
if (ComponentsWithSockets.Num() == 0 || !Actor)
{
return;
}
// Pop up a component chooser
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
TSharedPtr< ILevelEditor > LevelEditor = LevelEditorModule.GetFirstLevelEditor();
TSharedPtr<SWidget> ComponentMenuWidget =
SNew(SComponentChooserPopup)
.Actor(Actor)
.OnComponentChosen_Lambda([Actor, LevelEditor, KeyEditor, ActorKey = ActorKey](FName InComponentName) mutable
{
// ActorKey is self-captured so that the lambda can mutate its copy.
ActorKey.ComponentName = InComponentName;
KeyEditor.SetValueWithNotify(ActorKey, EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
// Look for sockets to choose
USceneComponent* ComponentWithSockets = nullptr;
TInlineComponentArray<USceneComponent*> Components(Actor);
for (USceneComponent* Component : Components)
{
if (Component && Component->GetFName() == InComponentName)
{
ComponentWithSockets = Component;
break;
}
}
if (!ComponentWithSockets)
{
return;
}
// Pop up a socket chooser
TSharedPtr<SWidget> SocketMenuWidget =
SNew(SSocketChooserPopup)
.SceneComponent(ComponentWithSockets)
.OnSocketChosen_Lambda([=](FName InSocketName) mutable
{
ActorKey.SocketName = InSocketName;
KeyEditor.SetValueWithNotify(ActorKey, EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately);
}
);
// Create as context menu
FSlateApplication::Get().PushMenu(
LevelEditor.ToSharedRef(),
FWidgetPath(),
SocketMenuWidget.ToSharedRef(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
);
}
);
// Create as context menu
FSlateApplication::Get().PushMenu(
LevelEditor.ToSharedRef(),
FWidgetPath(),
ComponentMenuWidget.ToSharedRef(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
);
};
auto GetCurrentValueLambda = [KeyEditor]() -> FMovieSceneActorReferenceKey
{
return KeyEditor.GetCurrentValue();
};
return SNew(SActorReferenceBox, Params.Sequencer)
.ActorReferenceKey_Lambda(GetCurrentValueLambda)
.OnSetActorReferenceKey_Lambda(OnSetCurrentValueLambda);
}
UMovieSceneKeyStructType* InstanceGeneratedStruct(FMovieSceneByteChannel* Channel, FSequencerKeyStructGenerator* Generator)
{
UEnum* ByteEnum = Channel->GetEnum();
if (!ByteEnum)
{
// No enum so just use the default (which will create a generated struct with a byte property)
return Generator->DefaultInstanceGeneratedStruct(FMovieSceneByteChannel::StaticStruct());
}
FName GeneratedTypeName = *FString::Printf(TEXT("MovieSceneByteChannel_%s"), *ByteEnum->GetName());
UMovieSceneKeyStructType* Existing = Generator->FindGeneratedStruct(GeneratedTypeName);
if (Existing)
{
return Existing;
}
UMovieSceneKeyStructType* NewStruct = FSequencerKeyStructGenerator::AllocateNewKeyStruct(FMovieSceneByteChannel::StaticStruct());
if (!NewStruct)
{
return nullptr;
}
FByteProperty* NewValueProperty = new FByteProperty(NewStruct, "Value", RF_NoFlags);
NewValueProperty->SetPropertyFlags(CPF_Edit);
NewValueProperty->SetMetaData("Category", TEXT("Key"));
NewValueProperty->ArrayDim = 1;
NewValueProperty->Enum = ByteEnum;
NewStruct->AddCppProperty(NewValueProperty);
NewStruct->DestValueProperty = NewValueProperty;
FSequencerKeyStructGenerator::FinalizeNewKeyStruct(NewStruct);
Generator->AddGeneratedStruct(GeneratedTypeName, NewStruct);
return NewStruct;
}
UMovieSceneKeyStructType* InstanceGeneratedStruct(FMovieSceneTimeWarpChannel* Channel, FSequencerKeyStructGenerator* Generator)
{
FName GeneratedTypeName("FMovieSceneTimeWarpChannelGeneratedStruct");
UMovieSceneKeyStructType* Existing = Generator->FindGeneratedStruct(GeneratedTypeName);
if (Existing)
{
return Existing;
}
UMovieSceneKeyStructType* NewStruct = FSequencerKeyStructGenerator::AllocateNewKeyStruct(FMovieSceneTimeWarpChannel::StaticStruct());
if (!NewStruct)
{
return nullptr;
}
FStructProperty* NewValueProperty = new FStructProperty(NewStruct, "Value", RF_NoFlags);
NewValueProperty->SetPropertyFlags(CPF_Edit);
NewValueProperty->SetMetaData("Category", TEXT("Key"));
NewValueProperty->Struct = TBaseStructure<FFrameNumber>::Get();
NewStruct->AddCppProperty(NewValueProperty);
NewStruct->DestValueProperty = NewValueProperty;
FSequencerKeyStructGenerator::FinalizeNewKeyStruct(NewStruct);
Generator->AddGeneratedStruct(GeneratedTypeName, NewStruct);
return NewStruct;
}
void PostConstructKeyInstance(const TMovieSceneChannelHandle<FMovieSceneTimeWarpChannel>& ChannelHandle, FKeyHandle InHandle, FStructOnScope* Struct)
{
const UMovieSceneKeyStructType* GeneratedStructType = CastChecked<const UMovieSceneKeyStructType>(Struct->GetStruct());
uint8* StructMemory = Struct->GetStructMemory();
FStructProperty* ValueProperty = CastFieldChecked<FStructProperty>(GeneratedStructType->DestValueProperty.Get());
FStructProperty* TimeProperty = CastFieldChecked<FStructProperty>(GeneratedStructType->DestTimeProperty.Get());
const FFrameNumber* TimeAddress = TimeProperty->ContainerPtrToValuePtr<FFrameNumber>(StructMemory);
FFrameNumber* ValueAddress = ValueProperty->ContainerPtrToValuePtr<FFrameNumber>(StructMemory);
// It is safe to capture the property and address in this lambda because the lambda is owned by the struct itself, so cannot be invoked if the struct has been destroyed
auto CopyInstanceToKeyLambda = [ChannelHandle, InHandle, GeneratedStructType, ValueProperty, ValueAddress, TimeAddress](const FPropertyChangedEvent&)
{
if (FMovieSceneTimeWarpChannel* DestinationChannel = ChannelHandle.Get())
{
const int32 KeyIndex = DestinationChannel->GetData().GetIndex(InHandle);
if (KeyIndex != INDEX_NONE)
{
DestinationChannel->GetData().GetValues()[KeyIndex].Value = ValueAddress->Value;
// Set the new key time
DestinationChannel->SetKeyTime(InHandle, *TimeAddress);
}
}
};
FGeneratedMovieSceneKeyStruct* KeyStruct = reinterpret_cast<FGeneratedMovieSceneKeyStruct*>(Struct->GetStructMemory());
KeyStruct->OnPropertyChangedEvent = CopyInstanceToKeyLambda;
// Copy the initial value for the struct
FMovieSceneTimeWarpChannel* Channel = ChannelHandle.Get();
if (Channel)
{
// Copy the initial value into the struct
const int32 KeyIndex = Channel->GetData().GetIndex(InHandle);
if (KeyIndex != INDEX_NONE)
{
double InitialTime = Channel->GetData().GetValues()[KeyIndex].Value;
*ValueAddress = FFrameTime::FromDecimal(InitialTime).FloorToFrame();
}
}
}
UMovieSceneKeyStructType* InstanceGeneratedStruct(FMovieSceneObjectPathChannel* Channel, FSequencerKeyStructGenerator* Generator)
{
UClass* PropertyClass = Channel->GetPropertyClass();
if (!PropertyClass)
{
// No specific property class so just use the default (which will create a generated struct with an object property)
return Generator->DefaultInstanceGeneratedStruct(FMovieSceneObjectPathChannel::StaticStruct());
}
FName GeneratedTypeName = *FString::Printf(TEXT("MovieSceneObjectPathChannel_%s"), *PropertyClass->GetName());
UMovieSceneKeyStructType* Existing = Generator->FindGeneratedStruct(GeneratedTypeName);
if (Existing)
{
return Existing;
}
UMovieSceneKeyStructType* NewStruct = FSequencerKeyStructGenerator::AllocateNewKeyStruct(FMovieSceneObjectPathChannel::StaticStruct());
if (!NewStruct)
{
return nullptr;
}
FObjectProperty* NewValueProperty = new FObjectProperty(NewStruct, "Value", RF_NoFlags);
NewValueProperty->SetPropertyFlags(CPF_Edit | CPF_TObjectPtrWrapper);
NewValueProperty->SetMetaData("Category", TEXT("Key"));
NewValueProperty->PropertyClass = PropertyClass;
NewValueProperty->ArrayDim = 1;
NewStruct->AddCppProperty(NewValueProperty);
NewStruct->DestValueProperty = NewValueProperty;
FSequencerKeyStructGenerator::FinalizeNewKeyStruct(NewStruct);
Generator->AddGeneratedStruct(GeneratedTypeName, NewStruct);
return NewStruct;
}
void PostConstructKeyInstance(const TMovieSceneChannelHandle<FMovieSceneObjectPathChannel>& ChannelHandle, FKeyHandle InHandle, FStructOnScope* Struct)
{
const UMovieSceneKeyStructType* GeneratedStructType = CastChecked<const UMovieSceneKeyStructType>(Struct->GetStruct());
uint8* StructMemory = Struct->GetStructMemory();
FObjectProperty* ValueProperty = CastFieldChecked<FObjectProperty>(GeneratedStructType->DestValueProperty.Get());
FStructProperty* TimeProperty = CastFieldChecked<FStructProperty>(GeneratedStructType->DestTimeProperty.Get());
const FFrameNumber* TimeAddress = TimeProperty->ContainerPtrToValuePtr<FFrameNumber>(StructMemory);
void* ValueAddress = ValueProperty->ContainerPtrToValuePtr<uint8>(StructMemory);
// It is safe to capture the property and address in this lambda because the lambda is owned by the struct itself, so cannot be invoked if the struct has been destroyed
auto CopyInstanceToKeyLambda = [ChannelHandle, InHandle, GeneratedStructType, ValueProperty, ValueAddress, TimeAddress](const FPropertyChangedEvent&)
{
if (FMovieSceneObjectPathChannel* DestinationChannel = ChannelHandle.Get())
{
const int32 KeyIndex = DestinationChannel->GetData().GetIndex(InHandle);
if (KeyIndex != INDEX_NONE)
{
UObject* ObjectPropertyValue = ValueProperty->GetObjectPropertyValue(ValueAddress);
DestinationChannel->GetData().GetValues()[KeyIndex] = ObjectPropertyValue;
// Set the new key time
DestinationChannel->SetKeyTime(InHandle, *TimeAddress);
}
}
};
FGeneratedMovieSceneKeyStruct* KeyStruct = reinterpret_cast<FGeneratedMovieSceneKeyStruct*>(Struct->GetStructMemory());
KeyStruct->OnPropertyChangedEvent = CopyInstanceToKeyLambda;
// Copy the initial value for the struct
FMovieSceneObjectPathChannel* Channel = ChannelHandle.Get();
if (Channel)
{
// Copy the initial value into the struct
const int32 KeyIndex = Channel->GetData().GetIndex(InHandle);
if (KeyIndex != INDEX_NONE)
{
UObject* InitialObject = Channel->GetData().GetValues()[KeyIndex].Get();
ValueProperty->SetObjectPropertyValue(ValueAddress, InitialObject);
}
}
}
void PostConstructKeyInstance(const TMovieSceneChannelHandle<FMovieSceneDoubleChannel>& ChannelHandle, FKeyHandle InHandle, FStructOnScope* Struct)
{
bool bInvertValue = false;
FGeneratedMovieSceneKeyStruct* KeyStruct = reinterpret_cast<FGeneratedMovieSceneKeyStruct*>(Struct->GetStructMemory());
const UMovieSceneKeyStructType* GeneratedStructType = CastChecked<const UMovieSceneKeyStructType>(Struct->GetStruct());
void* StructMemory = Struct->GetStructMemory();
FStructProperty* ValueProperty = CastFieldChecked<FStructProperty>(GeneratedStructType->DestValueProperty.Get());
FMovieSceneDoubleChannel* Channel = ChannelHandle.Get();
if (Channel)
{
if (const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData())
{
bInvertValue = MetaData->bInvertValue;
}
const int32 KeyIndex = Channel->GetData().GetIndex(InHandle);
// Copy the initial value into the struct
if (KeyIndex != INDEX_NONE)
{
double InitialValue = Channel->GetData().GetValues()[KeyIndex].Value;
*ValueProperty->ContainerPtrToValuePtr<double>(StructMemory) = bInvertValue ? -InitialValue : InitialValue;
}
}
// It is safe to capture the property and address in this lambda because the lambda is owned by the struct itself, so cannot be invoked if the struct has been destroyed
auto CopyInstanceToKeyLambda = [ChannelHandle, InHandle, StructMemory, ValueProperty, bInvertValue](const FPropertyChangedEvent&)
{
if (FMovieSceneDoubleChannel* DestinationChannel = ChannelHandle.Get())
{
const int32 KeyIndex = DestinationChannel->GetData().GetIndex(InHandle);
if (KeyIndex != INDEX_NONE)
{
double Value = *ValueProperty->ContainerPtrToValuePtr<double>(StructMemory);
DestinationChannel->GetData().GetValues()[KeyIndex].Value = bInvertValue ? -Value : Value;
}
}
};
KeyStruct->OnPropertyChangedEvent = CopyInstanceToKeyLambda;
}
template<typename ChannelType>
void DrawKeysImpl(ChannelType* Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams)
{
using ChannelValueType = typename ChannelType::ChannelValueType;
static const FName CircleKeyBrushName("Sequencer.KeyCircle");
static const FName DiamondKeyBrushName("Sequencer.KeyDiamond");
static const FName SquareKeyBrushName("Sequencer.KeySquare");
static const FName TriangleKeyBrushName("Sequencer.KeyTriangle");
const FSlateBrush* CircleKeyBrush = FAppStyle::GetBrush(CircleKeyBrushName);
const FSlateBrush* DiamondKeyBrush = FAppStyle::GetBrush(DiamondKeyBrushName);
const FSlateBrush* SquareKeyBrush = FAppStyle::GetBrush(SquareKeyBrushName);
const FSlateBrush* TriangleKeyBrush = FAppStyle::GetBrush(TriangleKeyBrushName);
TMovieSceneChannelData<ChannelValueType> ChannelData = Channel->GetData();
TArrayView<const ChannelValueType> Values = ChannelData.GetValues();
FKeyDrawParams TempParams;
TempParams.BorderBrush = TempParams.FillBrush = DiamondKeyBrush;
TempParams.ConnectionStyle = EKeyConnectionStyle::Solid;
for (int32 Index = 0; Index < InKeyHandles.Num(); ++Index)
{
FKeyHandle Handle = InKeyHandles[Index];
const int32 KeyIndex = ChannelData.GetIndex(Handle);
ERichCurveInterpMode InterpMode = KeyIndex == INDEX_NONE ? RCIM_None : Values[KeyIndex].InterpMode.GetValue();
ERichCurveTangentMode TangentMode = KeyIndex == INDEX_NONE ? RCTM_None : Values[KeyIndex].TangentMode.GetValue();
TempParams.FillOffset = FVector2D(0.f, 0.f);
TempParams.ConnectionStyle = EKeyConnectionStyle::Solid;
switch (InterpMode)
{
case RCIM_Linear:
TempParams.BorderBrush = TempParams.FillBrush = TriangleKeyBrush;
TempParams.FillTint = FLinearColor(0.0f, 0.617f, 0.449f, 1.0f); // blueish green
TempParams.FillOffset = FVector2D(0.0f, 1.0f);
break;
case RCIM_Constant:
TempParams.BorderBrush = TempParams.FillBrush = SquareKeyBrush;
TempParams.FillTint = FLinearColor(0.0f, 0.445f, 0.695f, 1.0f); // blue
TempParams.ConnectionStyle = EKeyConnectionStyle::Dashed;
break;
case RCIM_Cubic:
TempParams.BorderBrush = TempParams.FillBrush = CircleKeyBrush;
switch (TangentMode)
{
case RCTM_SmartAuto: TempParams.FillTint = FLinearColor(0.759f, 0.176f, 0.67f, 1.0f);break; // little vermillion
case RCTM_Auto: TempParams.FillTint = FLinearColor(0.972f, 0.2f, 0.2f, 1.0f); break; // vermillion
case RCTM_Break: TempParams.FillTint = FLinearColor(0.336f, 0.703f, 0.5f, 0.91f); break; // sky blue
case RCTM_User: TempParams.FillTint = FLinearColor(0.797f, 0.473f, 0.5f, 0.652f); break; // reddish purple
default: TempParams.FillTint = FLinearColor(0.75f, 0.75f, 0.75f, 1.0f); break; // light gray
}
break;
default:
TempParams.BorderBrush = TempParams.FillBrush = DiamondKeyBrush;
TempParams.FillTint = FLinearColor(1.0f, 1.0f, 1.0f, 1.0f); // white
break;
}
OutKeyDrawParams[Index] = TempParams;
}
}
void DrawKeys(FMovieSceneFloatChannel* Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams)
{
DrawKeysImpl(Channel, InKeyHandles, InOwner, OutKeyDrawParams);
}
void DrawKeys(FMovieSceneDoubleChannel* Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams)
{
DrawKeysImpl(Channel, InKeyHandles, InOwner, OutKeyDrawParams);
}
void DrawKeys(FMovieSceneParticleChannel* Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams)
{
static const FName KeyLeftBrushName("Sequencer.KeyLeft");
static const FName KeyRightBrushName("Sequencer.KeyRight");
static const FName KeyDiamondBrushName("Sequencer.KeyDiamond");
const FSlateBrush* LeftKeyBrush = FAppStyle::GetBrush(KeyLeftBrushName);
const FSlateBrush* RightKeyBrush = FAppStyle::GetBrush(KeyRightBrushName);
const FSlateBrush* DiamondBrush = FAppStyle::GetBrush(KeyDiamondBrushName);
TMovieSceneChannelData<uint8> ChannelData = Channel->GetData();
for (int32 Index = 0; Index < InKeyHandles.Num(); ++Index)
{
FKeyHandle Handle = InKeyHandles[Index];
FKeyDrawParams Params;
Params.BorderBrush = Params.FillBrush = DiamondBrush;
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if ( KeyIndex != INDEX_NONE )
{
const EParticleKey Value = (EParticleKey)ChannelData.GetValues()[KeyIndex];
if ( Value == EParticleKey::Activate )
{
Params.BorderBrush = Params.FillBrush = LeftKeyBrush;
Params.FillOffset = FVector2D(-1.0f, 1.0f);
}
else if ( Value == EParticleKey::Deactivate )
{
Params.BorderBrush = Params.FillBrush = RightKeyBrush;
Params.FillOffset = FVector2D(1.0f, 1.0f);
}
}
OutKeyDrawParams[Index] = Params;
}
}
void DrawKeys(FMovieSceneEventChannel* Channel, TArrayView<const FKeyHandle> InKeyHandles, const UMovieSceneSection* InOwner, TArrayView<FKeyDrawParams> OutKeyDrawParams)
{
UMovieSceneEventSectionBase* EventSection = CastChecked<UMovieSceneEventSectionBase>(const_cast<UMovieSceneSection*>(InOwner));
FKeyDrawParams ValidEventParams, InvalidEventParams;
ValidEventParams.BorderBrush = ValidEventParams.FillBrush = FAppStyle::Get().GetBrush("Sequencer.KeyDiamond");
InvalidEventParams.FillBrush = FAppStyle::Get().GetBrush("Sequencer.KeyDiamond");
InvalidEventParams.BorderBrush = FAppStyle::Get().GetBrush("Sequencer.KeyDiamondBorder");
InvalidEventParams.FillTint = FLinearColor(1.f,1.f,1.f,.2f);
TMovieSceneChannelData<FMovieSceneEvent> ChannelData = Channel->GetData();
TArrayView<FMovieSceneEvent> Events = ChannelData.GetValues();
UMovieSceneSequence* Sequence = InOwner->GetTypedOuter<UMovieSceneSequence>();
FMovieSceneSequenceEditor* SequenceEditor = Sequence ? FMovieSceneSequenceEditor::Find(Sequence) : nullptr;
UBlueprint* SequenceDirectorBP = SequenceEditor ? SequenceEditor->FindDirectorBlueprint(Sequence) : nullptr;
for (int32 Index = 0; Index < InKeyHandles.Num(); ++Index)
{
int32 KeyIndex = ChannelData.GetIndex(InKeyHandles[Index]);
if (KeyIndex != INDEX_NONE && SequenceDirectorBP && FMovieSceneEventUtils::FindEndpoint(&Events[KeyIndex], EventSection, SequenceDirectorBP))
{
OutKeyDrawParams[Index] = ValidEventParams;
}
else
{
OutKeyDrawParams[Index] = InvalidEventParams;
}
}
}
template<typename ChannelType>
struct TCurveChannelKeyMenuExtension : TSharedFromThis<TCurveChannelKeyMenuExtension<ChannelType>>
{
using ChannelValueType = typename ChannelType::ChannelValueType;
TCurveChannelKeyMenuExtension(TWeakPtr<ISequencer> InSequencer, TArray<TExtendKeyMenuParams<ChannelType>>&& InChannels)
: WeakSequencer(InSequencer)
, ChannelAndHandles(MoveTemp(InChannels))
{}
void ExtendMenu(FMenuBuilder& MenuBuilder)
{
ISequencer* SequencerPtr = WeakSequencer.Pin().Get();
if (!SequencerPtr)
{
return;
}
TSharedRef<TCurveChannelKeyMenuExtension<ChannelType>> SharedThis = this->AsShared();
MenuBuilder.BeginSection("SequencerInterpolation", LOCTEXT("KeyInterpolationMenu", "Key Interpolation"));
{
MenuBuilder.AddMenuEntry(
LOCTEXT("SetKeyInterpolationSmartAuto", "Cubic (Smart Auto)"),
LOCTEXT("SetKeyInterpolationSmartAutoTooltip", "Set key interpolation to smart auto"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.IconKeySmartAuto"),
FUIAction(
FExecuteAction::CreateLambda([SharedThis] { SharedThis->SetInterpTangentMode(RCIM_Cubic, RCTM_SmartAuto); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis] { return SharedThis->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_SmartAuto); })),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetKeyInterpolationAuto", "Cubic (Auto)"),
LOCTEXT("SetKeyInterpolationAutoTooltip", "Set key interpolation to auto"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.IconKeyAuto"),
FUIAction(
FExecuteAction::CreateLambda([SharedThis]{ SharedThis->SetInterpTangentMode(RCIM_Cubic, RCTM_Auto); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis]{ return SharedThis->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_Auto); }) ),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetKeyInterpolationUser", "Cubic (User)"),
LOCTEXT("SetKeyInterpolationUserTooltip", "Set key interpolation to user"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.IconKeyUser"),
FUIAction(
FExecuteAction::CreateLambda([SharedThis]{ SharedThis->SetInterpTangentMode(RCIM_Cubic, RCTM_User); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis]{ return SharedThis->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_User); }) ),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetKeyInterpolationBreak", "Cubic (Break)"),
LOCTEXT("SetKeyInterpolationBreakTooltip", "Set key interpolation to break"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.IconKeyBreak"),
FUIAction(
FExecuteAction::CreateLambda([SharedThis]{ SharedThis->SetInterpTangentMode(RCIM_Cubic, RCTM_Break); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis]{ return SharedThis->IsInterpTangentModeSelected(RCIM_Cubic, RCTM_Break); }) ),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetKeyInterpolationLinear", "Linear"),
LOCTEXT("SetKeyInterpolationLinearTooltip", "Set key interpolation to linear"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.IconKeyLinear"),
FUIAction(
FExecuteAction::CreateLambda([SharedThis]{ SharedThis->SetInterpTangentMode(RCIM_Linear, RCTM_Auto); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis]{ return SharedThis->IsInterpTangentModeSelected(RCIM_Linear, RCTM_Auto); }) ),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetKeyInterpolationConstant", "Constant"),
LOCTEXT("SetKeyInterpolationConstantTooltip", "Set key interpolation to constant"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Sequencer.IconKeyConstant"),
FUIAction(
FExecuteAction::CreateLambda([SharedThis]{ SharedThis->SetInterpTangentMode(RCIM_Constant, RCTM_Auto); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis]{ return SharedThis->IsInterpTangentModeSelected(RCIM_Constant, RCTM_Auto); }) ),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}
MenuBuilder.EndSection(); // SequencerInterpolation
}
void SetInterpTangentMode(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode)
{
FScopedTransaction SetInterpTangentModeTransaction(NSLOCTEXT("Sequencer", "SetInterpTangentMode_Transaction", "Set Interpolation and Tangent Mode"));
bool bAnythingChanged = false;
for (const TExtendKeyMenuParams<ChannelType>& Channel : ChannelAndHandles)
{
if (UMovieSceneSignedObject* OwningObject = Cast<UMovieSceneSignedObject>(Channel.WeakOwner.Get()))
{
OwningObject->Modify();
}
if (ChannelType* ChannelPtr = Channel.Channel.Get())
{
TMovieSceneChannelData<ChannelValueType> ChannelData = ChannelPtr->GetData();
TArrayView<ChannelValueType> Values = ChannelData.GetValues();
for (FKeyHandle Handle : Channel.Handles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE)
{
Values[KeyIndex].InterpMode = InterpMode;
Values[KeyIndex].TangentMode = TangentMode;
bAnythingChanged = true;
}
}
ChannelPtr->AutoSetTangents();
}
}
if (bAnythingChanged)
{
if (ISequencer* Sequencer = WeakSequencer.Pin().Get())
{
Sequencer->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
}
}
bool IsInterpTangentModeSelected(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode) const
{
for (const TExtendKeyMenuParams<ChannelType>& Channel : ChannelAndHandles)
{
ChannelType* ChannelPtr = Channel.Channel.Get();
if (ChannelPtr)
{
TMovieSceneChannelData<ChannelValueType> ChannelData = ChannelPtr->GetData();
TArrayView<ChannelValueType> Values = ChannelData.GetValues();
for (FKeyHandle Handle : Channel.Handles)
{
int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex == INDEX_NONE || Values[KeyIndex].InterpMode != InterpMode || Values[KeyIndex].TangentMode != TangentMode)
{
return false;
}
}
}
}
return true;
}
private:
/** Hidden AsShared() methods to prevent CreateSP delegate use since this extender disappears with its menu. */
using TSharedFromThis<TCurveChannelKeyMenuExtension<ChannelType>>::AsShared;
TWeakPtr<ISequencer> WeakSequencer;
TArray<TExtendKeyMenuParams<ChannelType>> ChannelAndHandles;
};
struct FCurveChannelSectionMenuExtension : TSharedFromThis<FCurveChannelSectionMenuExtension>, ISidebarChannelExtension
{
static TSharedRef<FCurveChannelSectionMenuExtension> GetOrCreate(TWeakPtr<ISequencer> InSequencer)
{
TSharedPtr<FCurveChannelSectionMenuExtension> CurrentExtension = WeakCurrentExtension.Pin();
if (!CurrentExtension)
{
CurrentExtension = MakeShared<FCurveChannelSectionMenuExtension>(InSequencer);
WeakCurrentExtension = CurrentExtension;
}
else
{
ensure(CurrentExtension->NumCurveChannelTypes > 0);
ensure(CurrentExtension->WeakSequencer == InSequencer);
}
return CurrentExtension.ToSharedRef();
}
FCurveChannelSectionMenuExtension(TWeakPtr<ISequencer> InSequencer)
: WeakSequencer(InSequencer)
, NumCurveChannelTypes(0)
, bMenusAdded(false)
{
}
virtual ~FCurveChannelSectionMenuExtension() {}
void AddSections(const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections)
{
WeakSections = TSet(InWeakSections);
++NumCurveChannelTypes;
}
virtual TSharedPtr<ISidebarChannelExtension> ExtendMenu(FMenuBuilder& MenuBuilder, const bool bInSubMenu) override
{
--NumCurveChannelTypes;
if (bMenusAdded)
{
// Only add menus once -- not once per curve channel type (float, double, etc)
return nullptr;
}
bMenusAdded = true;
ISequencer* SequencerPtr = WeakSequencer.Pin().Get();
if (!SequencerPtr)
{
return nullptr;
}
TSharedRef<FCurveChannelSectionMenuExtension> SharedThis = this->AsShared();
MenuBuilder.AddSubMenu(
LOCTEXT("CurveChannelsMenuLabel", "Curve Channels"),
LOCTEXT("CurveChannelsMenuToolTip", "Edit parameters for curve channels"),
FNewMenuDelegate::CreateLambda([SharedThis](FMenuBuilder& SubMenuBuilder)
{
SubMenuBuilder.AddSubMenu(
LOCTEXT("SetPreInfinityExtrap", "Pre-Infinity"),
LOCTEXT("SetPreInfinityExtrapTooltip", "Set pre-infinity extrapolation"),
FNewMenuDelegate::CreateLambda([SharedThis](FMenuBuilder& SubMenuBuilder){ SharedThis->AddExtrapolationMenu(SubMenuBuilder, true); })
);
SubMenuBuilder.AddSubMenu(
LOCTEXT("SetPostInfinityExtrap", "Post-Infinity"),
LOCTEXT("SetPostInfinityExtrapTooltip", "Set post-infinity extrapolation"),
FNewMenuDelegate::CreateLambda([SharedThis](FMenuBuilder& SubMenuBuilder){ SharedThis->AddExtrapolationMenu(SubMenuBuilder, false); })
);
SubMenuBuilder.AddSubMenu(
LOCTEXT("DisplayOpyions", "Display"),
LOCTEXT("DisplayOptionsTooltip", "Display options"),
FNewMenuDelegate::CreateLambda([SharedThis](FMenuBuilder& SubMenuBuilder){ SharedThis->AddDisplayOptionsMenu(SubMenuBuilder); })
);
if (SharedThis->CanInterpolateLinearKeys())
{
SubMenuBuilder.AddMenuEntry(
LOCTEXT("InterpolateLinearKeys", "Interpolate Linear Keys"),
LOCTEXT("InterpolateLinearKeysTooltip", "Interpolate linear keys"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis] { SharedThis->ToggleInterpolateLinearKeys(); }),
FCanExecuteAction(),
FGetActionCheckState::CreateLambda([SharedThis] { return SharedThis->IsInterpolateLinearKeys(); })
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
}
}));
return SharedThis;
}
void AddDisplayOptionsMenu(FMenuBuilder& MenuBuilder)
{
TSharedRef<FCurveChannelSectionMenuExtension> SharedThis = this->AsShared();
ISequencer* Sequencer = WeakSequencer.Pin().Get();
if (!Sequencer)
{
return;
}
USequencerSettings* Settings = Sequencer->GetSequencerSettings();
if (!Settings)
{
return;
}
// Menu entry for key area height
auto OnKeyAreaHeightChanged = [=](int32 NewValue) { Settings->SetKeyAreaHeightWithCurves((float)NewValue); };
auto GetKeyAreaHeight = [=]() { return (int)Settings->GetKeyAreaHeightWithCurves(); };
auto OnKeyAreaCurveNormalized = [=](FString KeyAreaName)
{
if (Settings->HasKeyAreaCurveExtents(KeyAreaName))
{
Settings->RemoveKeyAreaCurveExtents(KeyAreaName);
}
else
{
// Initialize to some arbitrary value
Settings->SetKeyAreaCurveExtents(KeyAreaName, 0.f, 6.f);
}
};
auto GetKeyAreaCurveNormalized = [=](FString KeyAreaName) { return !Settings->HasKeyAreaCurveExtents(KeyAreaName); };
auto OnKeyAreaCurveMinChanged = [=](double NewValue, FString KeyAreaName) { double CurveMin = 0.f; double CurveMax = 0.f; Settings->GetKeyAreaCurveExtents(KeyAreaName, CurveMin, CurveMax); Settings->SetKeyAreaCurveExtents(KeyAreaName, NewValue, CurveMax); };
auto GetKeyAreaCurveMin = [=](FString KeyAreaName) { double CurveMin = 0.f; double CurveMax = 0.f; Settings->GetKeyAreaCurveExtents(KeyAreaName, CurveMin, CurveMax); return CurveMin; };
auto OnKeyAreaCurveMaxChanged = [=](double NewValue, FString KeyAreaName) { double CurveMin = 0.f; double CurveMax = 0.f; Settings->GetKeyAreaCurveExtents(KeyAreaName, CurveMin, CurveMax); Settings->SetKeyAreaCurveExtents(KeyAreaName, CurveMin, NewValue); };
auto GetKeyAreaCurveMax = [=](FString KeyAreaName) { double CurveMin = 0.f; double CurveMax = 0.f; Settings->GetKeyAreaCurveExtents(KeyAreaName, CurveMin, CurveMax); return CurveMax; };
MenuBuilder.AddMenuEntry(
LOCTEXT("ToggleShowCurve", "Show Curve"),
LOCTEXT("ToggleShowCurveTooltip", "Toggle showing the curve in the track area"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis]{ SharedThis->ToggleShowCurve(); }),
FCanExecuteAction(),
FGetActionCheckState::CreateLambda([SharedThis]{ return SharedThis->IsShowCurve(); })
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
FString KeyAreaName;
TArray<const IKeyArea*> SelectedKeyAreas;
Sequencer->GetSelectedKeyAreas(SelectedKeyAreas);
for (const IKeyArea* KeyArea : SelectedKeyAreas)
{
if (KeyArea)
{
KeyAreaName = KeyArea->GetName().ToString();
break;
}
}
MenuBuilder.AddMenuEntry(
LOCTEXT("ToggleKeyAreaCurveNormalized", "Key Area Curve Normalized"),
LOCTEXT("ToggleKeyAreaCurveNormalizedTooltip", "Toggle showing the curve in the track area as normalized"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([=] { OnKeyAreaCurveNormalized(KeyAreaName); }),
FCanExecuteAction(FCanExecuteAction::CreateLambda([SharedThis]{ return SharedThis->IsAnyShowCurve(); })),
FIsActionChecked::CreateLambda([=] { return GetKeyAreaCurveNormalized(KeyAreaName); })
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddWidget(
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SNew(SSpacer)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(50.f)
.IsEnabled_Lambda([=]() { return SharedThis->IsAnyShowCurve() && Settings->HasKeyAreaCurveExtents(KeyAreaName); })
[
SNew(SSpinBox<double>)
.Style(&FAppStyle::GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
.OnValueCommitted_Lambda([=](double NewValue, ETextCommit::Type CommitType) { OnKeyAreaCurveMinChanged(NewValue, KeyAreaName); })
.OnValueChanged_Lambda([=](double NewValue) { OnKeyAreaCurveMinChanged(NewValue, KeyAreaName); })
.Value_Lambda([=]() -> double { return GetKeyAreaCurveMin(KeyAreaName); })
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(50.f)
.IsEnabled_Lambda([=]() { return SharedThis->IsAnyShowCurve() && Settings->HasKeyAreaCurveExtents(KeyAreaName); })
[
SNew(SSpinBox<double>)
.Style(&FAppStyle::GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
.OnValueCommitted_Lambda([=](double NewValue, ETextCommit::Type CommitType) { OnKeyAreaCurveMaxChanged(NewValue, KeyAreaName); })
.OnValueChanged_Lambda([=](double NewValue) { OnKeyAreaCurveMaxChanged(NewValue, KeyAreaName); })
.Value_Lambda([=]() -> double { return GetKeyAreaCurveMax(KeyAreaName); })
]
],
LOCTEXT("KeyAreaCurveRangeText", "Key Area Curve Range")
);
MenuBuilder.AddWidget(
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SNew(SSpacer)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SBox)
.WidthOverride(50.f)
[
SNew(SSpinBox<int32>)
.Style(&FAppStyle::GetWidgetStyle<FSpinBoxStyle>("Sequencer.HyperlinkSpinBox"))
.OnValueCommitted_Lambda([=](int32 Value, ETextCommit::Type CommitType) { OnKeyAreaHeightChanged(Value); })
.OnValueChanged_Lambda(OnKeyAreaHeightChanged)
.MinValue(15)
.MaxValue(300)
.Value_Lambda([=]() -> int32 { return GetKeyAreaHeight(); })
]
],
LOCTEXT("KeyAreaHeightText", "Key Area Height")
);
}
void AddExtrapolationMenu(FMenuBuilder& MenuBuilder, bool bPreInfinity)
{
TSharedRef<FCurveChannelSectionMenuExtension> SharedThis = this->AsShared();
MenuBuilder.AddMenuEntry(
LOCTEXT("SetExtrapCycle", "Cycle"),
LOCTEXT("SetExtrapCycleTooltip", "Set extrapolation cycle"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis, bPreInfinity]{ SharedThis->SetExtrapolationMode(RCCE_Cycle, bPreInfinity); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis, bPreInfinity]{ return SharedThis->IsExtrapolationModeSelected(RCCE_Cycle, bPreInfinity); })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetExtrapCycleWithOffset", "Cycle with Offset"),
LOCTEXT("SetExtrapCycleWithOffsetTooltip", "Set extrapolation cycle with offset"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis, bPreInfinity]{ SharedThis->SetExtrapolationMode(RCCE_CycleWithOffset, bPreInfinity); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis, bPreInfinity]{ return SharedThis->IsExtrapolationModeSelected(RCCE_CycleWithOffset, bPreInfinity); })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetExtrapOscillate", "Oscillate"),
LOCTEXT("SetExtrapOscillateTooltip", "Set extrapolation oscillate"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis, bPreInfinity]{ SharedThis->SetExtrapolationMode(RCCE_Oscillate, bPreInfinity); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis, bPreInfinity]{ return SharedThis->IsExtrapolationModeSelected(RCCE_Oscillate, bPreInfinity); })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetExtrapLinear", "Linear"),
LOCTEXT("SetExtrapLinearTooltip", "Set extrapolation linear"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis, bPreInfinity]{ SharedThis->SetExtrapolationMode(RCCE_Linear, bPreInfinity); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis, bPreInfinity]{ return SharedThis->IsExtrapolationModeSelected(RCCE_Linear, bPreInfinity); })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
MenuBuilder.AddMenuEntry(
LOCTEXT("SetExtrapConstant", "Constant"),
LOCTEXT("SetExtrapConstantTooltip", "Set extrapolation constant"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([SharedThis, bPreInfinity]{ SharedThis->SetExtrapolationMode(RCCE_Constant, bPreInfinity); }),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([SharedThis, bPreInfinity]{ return SharedThis->IsExtrapolationModeSelected(RCCE_Constant, bPreInfinity); })
),
NAME_None,
EUserInterfaceActionType::RadioButton
);
}
void GetChannels(TArray<FMovieSceneFloatChannel*>& FloatChannels, TArray<FMovieSceneDoubleChannel*>& DoubleChannels,
TArray<FMovieSceneIntegerChannel*>& IntegerChannels, TArray<FMovieSceneBoolChannel*>& BoolChannels,
TArray<FMovieSceneByteChannel*>& ByteChannels) const
{
ISequencer* Sequencer = WeakSequencer.Pin().Get();
if (!Sequencer)
{
return;
}
// Get selected channels
TArray<const IKeyArea*> KeyAreas;
Sequencer->GetSelectedKeyAreas(KeyAreas);
for (const IKeyArea* KeyArea : KeyAreas)
{
FMovieSceneChannelHandle Handle = KeyArea->GetChannel();
if (Handle.GetChannelTypeName() == FMovieSceneFloatChannel::StaticStruct()->GetFName())
{
FMovieSceneFloatChannel* Channel = static_cast<FMovieSceneFloatChannel*>(Handle.Get());
FloatChannels.Add(Channel);
}
else if (Handle.GetChannelTypeName() == FMovieSceneDoubleChannel::StaticStruct()->GetFName())
{
FMovieSceneDoubleChannel* Channel = static_cast<FMovieSceneDoubleChannel*>(Handle.Get());
DoubleChannels.Add(Channel);
}
else if (Handle.GetChannelTypeName() == FMovieSceneIntegerChannel::StaticStruct()->GetFName())
{
FMovieSceneIntegerChannel* Channel = static_cast<FMovieSceneIntegerChannel*>(Handle.Get());
IntegerChannels.Add(Channel);
}
else if (Handle.GetChannelTypeName() == FMovieSceneBoolChannel::StaticStruct()->GetFName())
{
FMovieSceneBoolChannel* Channel = static_cast<FMovieSceneBoolChannel*>(Handle.Get());
BoolChannels.Add(Channel);
}
else if (Handle.GetChannelTypeName() == FMovieSceneByteChannel::StaticStruct()->GetFName())
{
FMovieSceneByteChannel* Channel = static_cast<FMovieSceneByteChannel*>(Handle.Get());
ByteChannels.Add(Channel);
}
}
// Otherwise, the channels of all the sections
if (FloatChannels.Num() + DoubleChannels.Num() + IntegerChannels.Num() + BoolChannels.Num() + ByteChannels.Num() == 0)
{
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (FMovieSceneFloatChannel* Channel : ChannelProxy.GetChannels<FMovieSceneFloatChannel>())
{
FloatChannels.Add(Channel);
}
for (FMovieSceneDoubleChannel* Channel : ChannelProxy.GetChannels<FMovieSceneDoubleChannel>())
{
DoubleChannels.Add(Channel);
}
for (FMovieSceneIntegerChannel* Channel : ChannelProxy.GetChannels<FMovieSceneIntegerChannel>())
{
IntegerChannels.Add(Channel);
}
for (FMovieSceneBoolChannel* Channel : ChannelProxy.GetChannels<FMovieSceneBoolChannel>())
{
BoolChannels.Add(Channel);
}
for (FMovieSceneByteChannel* Channel : ChannelProxy.GetChannels<FMovieSceneByteChannel>())
{
ByteChannels.Add(Channel);
}
}
}
}
}
void SetExtrapolationMode(ERichCurveExtrapolation ExtrapMode, bool bPreInfinity)
{
TArray<FMovieSceneFloatChannel*> FloatChannels;
TArray<FMovieSceneDoubleChannel*> DoubleChannels;
TArray<FMovieSceneIntegerChannel*> IntegerChannels;
TArray<FMovieSceneBoolChannel*> BoolChannels;
TArray<FMovieSceneByteChannel*> ByteChannels;
GetChannels(FloatChannels, DoubleChannels, IntegerChannels, BoolChannels, ByteChannels);
if (FloatChannels.Num() + DoubleChannels.Num() + IntegerChannels.Num() + BoolChannels.Num() + ByteChannels.Num() == 0)
{
return;
}
FScopedTransaction Transaction(LOCTEXT("SetExtrapolationMode_Transaction", "Set Extrapolation Mode"));
bool bAnythingChanged = false;
// Modify all sections
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
Section->Modify();
}
}
// Apply to all channels
for (FMovieSceneFloatChannel* Channel : FloatChannels)
{
TEnumAsByte<ERichCurveExtrapolation>& DestExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
DestExtrap = ExtrapMode;
bAnythingChanged = true;
}
for (FMovieSceneDoubleChannel* Channel : DoubleChannels)
{
TEnumAsByte<ERichCurveExtrapolation>& DestExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
DestExtrap = ExtrapMode;
bAnythingChanged = true;
}
for (FMovieSceneIntegerChannel* Channel : IntegerChannels)
{
TEnumAsByte<ERichCurveExtrapolation>& DestExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
DestExtrap = ExtrapMode;
bAnythingChanged = true;
}
for (FMovieSceneBoolChannel* Channel : BoolChannels)
{
TEnumAsByte<ERichCurveExtrapolation>& DestExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
DestExtrap = ExtrapMode;
bAnythingChanged = true;
}
for (FMovieSceneByteChannel* Channel : ByteChannels)
{
TEnumAsByte<ERichCurveExtrapolation>& DestExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
DestExtrap = ExtrapMode;
bAnythingChanged = true;
}
if (bAnythingChanged)
{
if (ISequencer* Sequencer = WeakSequencer.Pin().Get())
{
Sequencer->NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
}
else
{
Transaction.Cancel();
}
}
bool IsExtrapolationModeSelected(ERichCurveExtrapolation ExtrapMode, bool bPreInfinity) const
{
TArray<FMovieSceneFloatChannel*> FloatChannels;
TArray<FMovieSceneDoubleChannel*> DoubleChannels;
TArray<FMovieSceneIntegerChannel*> IntegerChannels;
TArray<FMovieSceneBoolChannel*> BoolChannels;
TArray<FMovieSceneByteChannel*> ByteChannels;
GetChannels(FloatChannels, DoubleChannels, IntegerChannels, BoolChannels, ByteChannels);
for (FMovieSceneFloatChannel* Channel : FloatChannels)
{
ERichCurveExtrapolation SourceExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
if (SourceExtrap != ExtrapMode)
{
return false;
}
}
for (FMovieSceneDoubleChannel* Channel : DoubleChannels)
{
ERichCurveExtrapolation SourceExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
if (SourceExtrap != ExtrapMode)
{
return false;
}
}
for (FMovieSceneIntegerChannel* Channel : IntegerChannels)
{
ERichCurveExtrapolation SourceExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
if (SourceExtrap != ExtrapMode)
{
return false;
}
}
for (FMovieSceneBoolChannel* Channel : BoolChannels)
{
ERichCurveExtrapolation SourceExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
if (SourceExtrap != ExtrapMode)
{
return false;
}
}
for (FMovieSceneByteChannel* Channel : ByteChannels)
{
ERichCurveExtrapolation SourceExtrap = bPreInfinity ? Channel->PreInfinityExtrap : Channel->PostInfinityExtrap;
if (SourceExtrap != ExtrapMode)
{
return false;
}
}
return true;
}
bool CanInterpolateLinearKeys() const
{
ISequencer* Sequencer = WeakSequencer.Pin().Get();
if (!Sequencer)
{
return false;
}
TArray<FMovieSceneIntegerChannel*> IntegerChannels;
GetTypedChannels<FMovieSceneIntegerChannel>(Sequencer, WeakSections, IntegerChannels);
return IntegerChannels.Num() > 0;
}
void ToggleInterpolateLinearKeys()
{
ISequencer* Sequencer = WeakSequencer.Pin().Get();
if (!Sequencer)
{
return;
}
TArray<FMovieSceneIntegerChannel*> IntegerChannels;
GetTypedChannels<FMovieSceneIntegerChannel>(Sequencer, WeakSections, IntegerChannels);
FScopedTransaction Transaction(LOCTEXT("ToggleInterpolateLinearKeys_Transaction", "Toggle Interpolate Linear Keys"));
bool bAnythingChanged = false;
// Modify all sections
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
Section->Modify();
}
}
for (FMovieSceneIntegerChannel* Channel : IntegerChannels)
{
bAnythingChanged = true;
Channel->bInterpolateLinearKeys = !Channel->bInterpolateLinearKeys;
}
if (!bAnythingChanged)
{
Transaction.Cancel();
}
}
ECheckBoxState IsInterpolateLinearKeys()
{
ISequencer* Sequencer = WeakSequencer.Pin().Get();
if (!Sequencer)
{
return ECheckBoxState::Undetermined;
}
TArray<FMovieSceneIntegerChannel*> IntegerChannels;
GetTypedChannels<FMovieSceneIntegerChannel>(Sequencer, WeakSections, IntegerChannels);
int32 NumInterpolatedAndNotInterpolated[2] = { 0, 0 };
for (FMovieSceneIntegerChannel* Channel : IntegerChannels)
{
NumInterpolatedAndNotInterpolated[Channel->bInterpolateLinearKeys ? 0 : 1]++;
}
if (NumInterpolatedAndNotInterpolated[0] == 0 && NumInterpolatedAndNotInterpolated[1] > 0) // No curve showed, some hidden
{
return ECheckBoxState::Unchecked;
}
else if (NumInterpolatedAndNotInterpolated[0] > 0 && NumInterpolatedAndNotInterpolated[1] == 0) // Some curves showed, none hidden
{
return ECheckBoxState::Checked;
}
return ECheckBoxState::Undetermined; // Mixed states, or no curves
}
void ToggleShowCurve()
{
const ECheckBoxState CurrentState = IsShowCurve();
const bool bShowCurve = (CurrentState != ECheckBoxState::Checked); // If unchecked or mixed, check it
FScopedTransaction Transaction(LOCTEXT("ToggleShowCurve_Transaction", "Toggle Show Curve"));
bool bAnythingChanged = false;
// Modify all sections
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
Section->Modify();
}
}
// Apply to all channels
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (FMovieSceneFloatChannel* Channel : ChannelProxy.GetChannels<FMovieSceneFloatChannel>())
{
if (Channel)
{
Channel->SetShowCurve(bShowCurve);
bAnythingChanged = true;
}
}
for (FMovieSceneDoubleChannel* Channel : ChannelProxy.GetChannels<FMovieSceneDoubleChannel>())
{
if (Channel)
{
Channel->SetShowCurve(bShowCurve);
bAnythingChanged = true;
}
}
}
}
if (!bAnythingChanged)
{
Transaction.Cancel();
}
}
ECheckBoxState IsShowCurve() const
{
int32 NumShowedAndHidden[2] = { 0, 0 };
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (FMovieSceneFloatChannel* Channel : ChannelProxy.GetChannels<FMovieSceneFloatChannel>())
{
if (Channel)
{
NumShowedAndHidden[Channel->GetShowCurve() ? 0 : 1]++;
}
}
for (FMovieSceneDoubleChannel* Channel : ChannelProxy.GetChannels<FMovieSceneDoubleChannel>())
{
if (Channel)
{
NumShowedAndHidden[Channel->GetShowCurve() ? 0 : 1]++;
}
}
}
}
if (NumShowedAndHidden[0] == 0 && NumShowedAndHidden[1] > 0) // No curve showed, some hidden
{
return ECheckBoxState::Unchecked;
}
else if (NumShowedAndHidden[0] > 0 && NumShowedAndHidden[1] == 0) // Some curves showed, none hidden
{
return ECheckBoxState::Checked;
}
return ECheckBoxState::Undetermined; // Mixed states, or no curves
}
bool IsAnyShowCurve() const
{
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : WeakSections)
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (FMovieSceneFloatChannel* Channel : ChannelProxy.GetChannels<FMovieSceneFloatChannel>())
{
if (Channel && Channel->GetShowCurve())
{
return true;
}
}
for (FMovieSceneDoubleChannel* Channel : ChannelProxy.GetChannels<FMovieSceneDoubleChannel>())
{
if (Channel && Channel->GetShowCurve())
{
return true;
}
}
}
}
return false;
}
private:
/** Hidden AsShared() methods to prevent CreateSP delegate use since this extender disappears with its menu. */
using TSharedFromThis<FCurveChannelSectionMenuExtension>::AsShared;
/** Held weekly so that only the context menu owns the instance, and it gets naturally deleted when the menu closes */
static TWeakPtr<FCurveChannelSectionMenuExtension> WeakCurrentExtension;
TWeakPtr<ISequencer> WeakSequencer;
TSet<TWeakObjectPtr<UMovieSceneSection>> WeakSections;
int32 NumCurveChannelTypes;
bool bMenusAdded;
};
TWeakPtr<FCurveChannelSectionMenuExtension> FCurveChannelSectionMenuExtension::WeakCurrentExtension;
void ExtendSectionMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneFloatChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
MenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, true); }));
}
void ExtendSectionMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneDoubleChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
MenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, true); }));
}
void ExtendSectionMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneIntegerChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
MenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, true); }));
}
void ExtendSectionMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneBoolChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
MenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, true); }));
}
void ExtendSectionMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneByteChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
MenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, true); }));
}
TSharedPtr<ISidebarChannelExtension> ExtendSidebarMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> InMenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneFloatChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
InMenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, false); }));
return Extension;
}
TSharedPtr<ISidebarChannelExtension> ExtendSidebarMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> InMenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneDoubleChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
InMenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, false); }));
return Extension;
}
TSharedPtr<ISidebarChannelExtension> ExtendSidebarMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> InMenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneIntegerChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
InMenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, false); }));
return Extension;
}
TSharedPtr<ISidebarChannelExtension> ExtendSidebarMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> InMenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneBoolChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
InMenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, false); }));
return Extension;
}
TSharedPtr<ISidebarChannelExtension> ExtendSidebarMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> InMenuExtender, TArray<TMovieSceneChannelHandle<FMovieSceneByteChannel>>&& Channels, const TArray<TWeakObjectPtr<UMovieSceneSection>>& InWeakSections, TWeakPtr<ISequencer> InWeakSequencer)
{
TSharedRef<FCurveChannelSectionMenuExtension> Extension = FCurveChannelSectionMenuExtension::GetOrCreate(InWeakSequencer);
Extension->AddSections(InWeakSections);
InMenuExtender->AddMenuExtension("SequencerChannels", EExtensionHook::First, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder, false); }));
return Extension;
}
void ExtendKeyMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TExtendKeyMenuParams<FMovieSceneFloatChannel>>&& Channels, TWeakPtr<ISequencer> InSequencer)
{
using ExtensionType = TCurveChannelKeyMenuExtension<FMovieSceneFloatChannel>;
TSharedRef<ExtensionType> Extension = MakeShared<ExtensionType>(InSequencer, MoveTemp(Channels));
MenuExtender->AddMenuExtension("SequencerKeyEdit", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder); }));
}
void ExtendKeyMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TExtendKeyMenuParams<FMovieSceneDoubleChannel>>&& Channels, TWeakPtr<ISequencer> InSequencer)
{
using ExtensionType = TCurveChannelKeyMenuExtension<FMovieSceneDoubleChannel>;
TSharedRef<ExtensionType> Extension = MakeShared<ExtensionType>(InSequencer, MoveTemp(Channels));
MenuExtender->AddMenuExtension("SequencerKeyEdit", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder); }));
}
void ExtendKeyMenu(FMenuBuilder& OuterMenuBuilder, TSharedPtr<FExtender> MenuExtender, TArray<TExtendKeyMenuParams<FMovieSceneTimeWarpChannel>>&& Channels, TWeakPtr<ISequencer> InSequencer)
{
using ExtensionType = TCurveChannelKeyMenuExtension<FMovieSceneTimeWarpChannel>;
TSharedRef<ExtensionType> Extension = MakeShared<ExtensionType>(InSequencer, MoveTemp(Channels));
MenuExtender->AddMenuExtension("SequencerKeyEdit", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateLambda([Extension](FMenuBuilder& MenuBuilder) { Extension->ExtendMenu(MenuBuilder); }));
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneFloatChannel>& FloatChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (FloatChannel.GetMetaData() != nullptr && FloatChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FFloatChannelCurveModel>>(FloatChannel, Params.OwningSection, Params.Sequencer);
}
return MakeUnique<FFloatChannelCurveModel>(FloatChannel, Params.OwningSection, Params.Sequencer);
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneDoubleChannel>& DoubleChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (DoubleChannel.GetMetaData() != nullptr && DoubleChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FDoubleChannelCurveModel>>(DoubleChannel, Params.OwningSection, Params.Sequencer);
}
return MakeUnique<FDoubleChannelCurveModel>(DoubleChannel, Params.OwningSection, Params.OwningObject, Params.Sequencer);
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneIntegerChannel>& IntegerChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (IntegerChannel.GetMetaData() != nullptr && IntegerChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FIntegerChannelCurveModel>>(IntegerChannel, Params.OwningSection, Params.Sequencer);
}
return MakeUnique<FIntegerChannelCurveModel>(IntegerChannel, Params.OwningSection, Params.Sequencer);
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneBoolChannel>& BoolChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (BoolChannel.GetMetaData() != nullptr && BoolChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FBoolChannelCurveModel>>(BoolChannel, Params.OwningSection, Params.Sequencer);
}
return MakeUnique<FBoolChannelCurveModel>(BoolChannel, Params.OwningSection, Params.Sequencer);
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneByteChannel>& ByteChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (ByteChannel.GetMetaData() != nullptr && ByteChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FByteChannelCurveModel>>(ByteChannel, Params.OwningSection, Params.Sequencer);
}
return MakeUnique<FByteChannelCurveModel>(ByteChannel, Params.OwningSection, Params.Sequencer);
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneEventChannel>& EventChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (EventChannel.GetMetaData() != nullptr && EventChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FEventChannelCurveModel>>(EventChannel, Params.OwningSection, Params.Sequencer);
}
return MakeUnique<FEventChannelCurveModel>(EventChannel, Params.OwningSection, Params.Sequencer);
}
TUniquePtr<FCurveModel> CreateCurveEditorModel(const TMovieSceneChannelHandle<FMovieSceneTimeWarpChannel>& TimeWarpChannel, const UE::Sequencer::FCreateCurveEditorModelParams& Params)
{
if (TimeWarpChannel.GetMetaData() != nullptr && TimeWarpChannel.GetMetaData()->bInvertValue)
{
return MakeUnique<UE::CurveEditor::TInvertedCurveModel<FTimeWarpChannelCurveModel>>(TimeWarpChannel, Params.OwningSection, Params.OwningObject, Params.Sequencer);
}
return MakeUnique<FTimeWarpChannelCurveModel>(TimeWarpChannel, Params.OwningSection, Params.OwningObject, Params.Sequencer);
}
bool ShouldShowCurve(const FMovieSceneFloatChannel* Channel, UMovieSceneSection* InSection)
{
return Channel->GetShowCurve();
}
bool ShouldShowCurve(const FMovieSceneDoubleChannel* Channel, UMovieSceneSection* InSection)
{
return Channel->GetShowCurve();
}
TSharedPtr<UE::Sequencer::FChannelModel> CreateChannelModel(const TMovieSceneChannelHandle<FMovieSceneTimeWarpChannel>& InChannelHandle, const UE::Sequencer::FSectionModel& InSection, FName InChannelName)
{
return MakeShared<UE::Sequencer::FTimeWarpChannelModel>(InChannelName, InSection.GetSectionInterface(), InChannelHandle);
}
#undef LOCTEXT_NAMESPACE