// Copyright Epic Games, Inc. All Rights Reserved. #include "IKeyArea.h" #include "ISequencerModule.h" #include "Modules/ModuleManager.h" #include "Widgets/SNullWidget.h" #include "ISequencerChannelInterface.h" #include "SequencerClipboardReconciler.h" #include "Tracks/MovieScenePropertyTrack.h" #include "Channels/MovieSceneChannel.h" #include "Channels/MovieSceneChannelProxy.h" #include "CurveModel.h" #include "ISequencer.h" #include "ISequencerSection.h" #include "MovieScene.h" #include "MovieSceneSequence.h" #include "CurveEditorSettings.h" #include "CurveEditorTypes.h" IKeyArea::IKeyArea(TWeakPtr InSection, FMovieSceneChannelHandle InChannel , const FCurveModelID& InCurveId) : TreeSerialNumber(0) , ChannelHandle(InChannel) , CurveModelID(MakePimpl(InCurveId)) { Reinitialize(InSection, InChannel); } void IKeyArea::Reinitialize(TWeakPtr InSection, FMovieSceneChannelHandle InChannel) { WeakSection = InSection; ChannelHandle = InChannel; Color = FLinearColor::White; if (const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData()) { Color = MetaData->Color; ChannelName = MetaData->Name; DisplayText = MetaData->DisplayText; } UMovieSceneSection* SectionObject = InSection.Pin()->GetSectionObject(); UMovieScenePropertyTrack* PropertyTrack = SectionObject->GetTypedOuter(); if (PropertyTrack && PropertyTrack->GetPropertyPath() != NAME_None) { PropertyBindings = FTrackInstancePropertyBindings(PropertyTrack->GetPropertyName(), PropertyTrack->GetPropertyPath().ToString()); } } FMovieSceneChannel* IKeyArea::ResolveChannel() const { return ChannelHandle.Get(); } UMovieSceneSection* IKeyArea::GetOwningSection() const { TSharedPtr SectionInterface = WeakSection.Pin(); return SectionInterface ? SectionInterface->GetSectionObject() : nullptr;; } UObject* IKeyArea::GetOwningObject() const { const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); UObject* OwningObject = MetaData ? MetaData->WeakOwningObject.Get() : nullptr; if (OwningObject) { return OwningObject; } return GetOwningSection(); } TSharedPtr IKeyArea::GetSectionInterface() const { return WeakSection.Pin(); } FName IKeyArea::GetName() const { return ChannelName; } void IKeyArea::SetName(FName InName) { ChannelName = InName; } ISequencerChannelInterface* IKeyArea::FindChannelEditorInterface() const { ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked("Sequencer"); ISequencerChannelInterface* EditorInterface = SequencerModule.FindChannelEditorInterface(ChannelHandle.GetChannelTypeName()); ensureMsgf(EditorInterface, TEXT("No channel interface found for type '%s'. Did you forget to call ISequencerModule::RegisterChannelInterface()?"), *ChannelHandle.GetChannelTypeName().ToString()); return EditorInterface; } FKeyHandle IKeyArea::AddOrUpdateKey(FFrameNumber Time, const FGuid& ObjectBindingID, ISequencer& InSequencer) { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = GetOwningSection(); if (const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData()) { Time -= MetaData->GetOffsetTime(Section); } // The extended editor data may be null, but is passed to the interface regardless const void* RawExtendedData = ChannelHandle.GetExtendedEditorData(); if (EditorInterface && Channel) { FTrackInstancePropertyBindings* BindingsPtr = PropertyBindings.IsSet() ? &PropertyBindings.GetValue() : nullptr; return EditorInterface->AddOrUpdateKey_Raw(Channel, Section, RawExtendedData, Time, InSequencer, ObjectBindingID, BindingsPtr); } return FKeyHandle(); } void IKeyArea::DeleteKeys(TArrayView InHandles, FFrameNumber InTime) { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); FMovieSceneChannel* Channel = ChannelHandle.Get(); if (Channel) { EditorInterface->DeleteKeys_Raw(Channel, InHandles, InTime); } } FKeyHandle IKeyArea::DuplicateKey(FKeyHandle InKeyHandle) const { FKeyHandle NewHandle = FKeyHandle::Invalid(); if (FMovieSceneChannel* Channel = ChannelHandle.Get()) { Channel->DuplicateKeys(TArrayView(&InKeyHandle, 1), TArrayView(&NewHandle, 1)); } return NewHandle; } void IKeyArea::SetKeyTimes(TArrayView InKeyHandles, TArrayView InKeyTimes) const { check(InKeyHandles.Num() == InKeyTimes.Num()); FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = GetOwningSection(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); if (!Channel || !MetaData || !Section) { return; } FFrameNumber KeyOffset = MetaData->GetOffsetTime(Section); if (KeyOffset != 0) { // Need to copy the array to offset TArray OffsetKeyTimes; OffsetKeyTimes.SetNumUninitialized(InKeyTimes.Num()); for (int32 i = 0; i < InKeyTimes.Num(); ++i) { OffsetKeyTimes[i] = InKeyTimes[i] - KeyOffset; } Channel->SetKeyTimes(InKeyHandles, OffsetKeyTimes); } else { Channel->SetKeyTimes(InKeyHandles, InKeyTimes); } } void IKeyArea::GetKeyTimes(TArrayView InKeyHandles, TArrayView OutTimes) const { FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = GetOwningSection(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); if (!Channel || !MetaData || !Section) { return; } Channel->GetKeyTimes(InKeyHandles, OutTimes); FFrameNumber KeyOffset = MetaData->GetOffsetTime(Section); if (KeyOffset != 0) { for (int32 i = 0; i < OutTimes.Num(); ++i) { OutTimes[i] += KeyOffset; } } } void IKeyArea::GetKeyInfo(TArray* OutHandles, TArray* OutTimes, const TRange& WithinRange) const { FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = GetOwningSection(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); if (!Channel || !MetaData || !Section) { return; } FFrameNumber KeyOffset = MetaData->GetOffsetTime(Section); TRange OffsetRange = WithinRange; if (KeyOffset != 0) { if (OffsetRange.HasLowerBound()) { OffsetRange.SetLowerBoundValue(OffsetRange.GetLowerBoundValue() - KeyOffset); } if (OffsetRange.HasUpperBound()) { OffsetRange.SetUpperBoundValue(OffsetRange.GetUpperBoundValue() - KeyOffset); } } Channel->GetKeys(OffsetRange, OutTimes, OutHandles); if (OutTimes && KeyOffset != 0) { const int32 Num = OutTimes->Num(); for (int32 i = 0; i < Num; ++i) { (*OutTimes)[i] += KeyOffset; } } } TSharedPtr IKeyArea::GetKeyStruct(FKeyHandle KeyHandle) const { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); if (EditorInterface) { return EditorInterface->GetKeyStruct_Raw(ChannelHandle, KeyHandle); } return nullptr; } int32 IKeyArea::DrawExtra(const FSequencerChannelPaintArgs& PaintArgs, int32 LayerId) const { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); if (EditorInterface) { FMovieSceneChannel* Channel = ChannelHandle.Get(); const UMovieSceneSection* OwningSection = GetOwningSection(); return EditorInterface->DrawExtra_Raw(Channel, OwningSection, PaintArgs, LayerId); } return LayerId; } void IKeyArea::DrawKeys(TArrayView InKeyHandles, TArrayView OutKeyDrawParams) { check(InKeyHandles.Num() == OutKeyDrawParams.Num()); ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* OwningSection = GetOwningSection(); if (EditorInterface && Channel && OwningSection) { return EditorInterface->DrawKeys_Raw(Channel, InKeyHandles, OwningSection, OutKeyDrawParams); } } bool IKeyArea::CanCreateKeyEditor() const { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); const FMovieSceneChannel* Channel = ChannelHandle.Get(); return EditorInterface && Channel && EditorInterface->CanCreateKeyEditor_Raw(Channel); } TSharedRef IKeyArea::CreateKeyEditor(TWeakPtr Sequencer, const FGuid& ObjectBindingID) { using namespace UE::Sequencer; ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); UMovieSceneSection* OwningSection = GetOwningSection(); TWeakPtr PropertyBindingsPtr; if (PropertyBindings.IsSet()) { PropertyBindingsPtr = TSharedPtr(AsShared(), &PropertyBindings.GetValue()); } const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); if (EditorInterface && OwningSection && MetaData) { FCreateKeyEditorParams Params{ OwningSection, MetaData->WeakOwningObject.Get(), Sequencer.Pin().ToSharedRef(), ObjectBindingID, PropertyBindingsPtr }; return EditorInterface->CreateKeyEditor_Raw(ChannelHandle, Params); } return SNullWidget::NullWidget; } void IKeyArea::CopyKeys(FMovieSceneClipboardBuilder& ClipboardBuilder, TArrayView KeyMask) const { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* OwningSection = GetOwningSection(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); if (EditorInterface && Channel && OwningSection && MetaData) { TGuardValue GuardKeyOffset(ClipboardBuilder.KeyOffset, ClipboardBuilder.KeyOffset + MetaData->GetOffsetTime(OwningSection)); EditorInterface->CopyKeys_Raw(Channel, OwningSection, ChannelName, ClipboardBuilder, KeyMask); } } TArray IKeyArea::PasteKeys(const FMovieSceneClipboardKeyTrack& KeyTrack, const FMovieSceneClipboardEnvironment& SrcEnvironment, const FSequencerPasteEnvironment& DstEnvironment) { TArray PastedKeys; ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* OwningSection = GetOwningSection(); UObject* OwningObject = GetOwningObject(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); if (EditorInterface && Channel && OwningSection && OwningObject && MetaData) { if (OwningSection->IsReadOnly()) { return PastedKeys; } OwningObject->Modify(); EditorInterface->PasteKeys_Raw(Channel, OwningSection, KeyTrack, SrcEnvironment, DstEnvironment, PastedKeys); FFrameNumber KeyOffset = MetaData->GetOffsetTime(OwningSection); if (KeyOffset != 0) { TArray KeyTimes; KeyTimes.SetNumUninitialized(PastedKeys.Num()); Channel->GetKeyTimes(PastedKeys, KeyTimes); for (int32 i = 0; i < KeyTimes.Num(); ++i) { KeyTimes[i] += KeyOffset; } Channel->SetKeyTimes(PastedKeys, KeyTimes); } } return PastedKeys; } FText GetOwningObjectBindingName(UMovieSceneTrack* InTrack, ISequencer& InSequencer) { check(InTrack); UMovieSceneSequence* FocusedSequence = InSequencer.GetFocusedMovieSceneSequence(); UMovieScene* MovieScene = FocusedSequence->GetMovieScene(); FGuid PossessableGuid; if (MovieScene->FindTrackBinding(*InTrack, PossessableGuid)) { return MovieScene->GetObjectDisplayName(PossessableGuid); } // Couldn't find an owning track, so must not be nestled inside of something! return FText(); } namespace UE { namespace MovieScene { void CleanUpCurveEditorFormatStringInline(FString& InStr) { for (int32 Index = 0; Index < InStr.GetCharArray().Num() - 1; Index++) { TCHAR CurrentChar = InStr.GetCharArray()[Index]; TCHAR NextChar = InStr.GetCharArray()[Index + 1]; if (CurrentChar == '.' && NextChar == '.') { const int32 NumToRemove = 1; InStr.RemoveAt(Index, NumToRemove, EAllowShrinking::No); Index--; } } // We can still end up with a "." at the end using the above logic if (InStr.GetCharArray()[InStr.GetCharArray().Num() - 1] == '.') { InStr.RemoveAt(InStr.GetCharArray().Num() - 1); } } }} TUniquePtr IKeyArea::CreateCurveEditorModel(TSharedRef InSequencer) const { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); UMovieSceneSection* OwningSection = GetOwningSection(); if (EditorInterface && OwningSection && ChannelHandle.Get() != nullptr) { const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); UE::Sequencer::FCreateCurveEditorModelParams Params = { OwningSection, MetaData->WeakOwningObject.Get(), InSequencer }; TUniquePtr CurveModel = EditorInterface->CreateCurveEditorModel_Raw(ChannelHandle, Params); if (CurveModel.IsValid()) { // Build long, short and context names for this curve to maximize information shown in the Curve Editor UI. UMovieSceneTrack* OwningTrack = OwningSection->GetTypedOuter(); FText OwningTrackName; FText ObjectBindingName; if (OwningTrack) { OwningTrackName = OwningTrack->GetDisplayName(); // This track might be inside an object binding and we'd like to prepend the object binding's name for more context. ObjectBindingName = GetOwningObjectBindingName(OwningTrack, *InSequencer); } // This is a FText (even though the end result is a FString) because all the incoming data sources are FText // And it's probably cheaper to convert one FText->FString instead of 4 shorter ones. FFormatNamedArguments FormatArgs; // Not all tracks have all the information so we need to format it differently depending on how many are valid. FormatArgs.Add(TEXT("ObjectBindingName"), ObjectBindingName); FormatArgs.Add(TEXT("OwningTrackName"), OwningTrackName); FormatArgs.Add(TEXT("GroupName"), MetaData->Group); FormatArgs.Add(TEXT("DisplayName"), DisplayText); FText FormatText = NSLOCTEXT("SequencerIKeyArea", "CurveLongDisplayNameFormat", "{ObjectBindingName}.{OwningTrackName}.{GroupName}.{DisplayName}"); FString FormattedText = FText::Format(FormatText, FormatArgs).ToString(); UE::MovieScene::CleanUpCurveEditorFormatStringInline(FormattedText); FText LongDisplayName = FText::FromString(FormattedText); const FText ShortDisplayName = DisplayText; FString IntentName = MetaData->IntentName.ToString(); if (IntentName.IsEmpty()) { IntentName = MetaData->Group.IsEmptyOrWhitespace() ? DisplayText.ToString() : FString::Format(TEXT("{0}.{1}"), { MetaData->Group.ToString(), DisplayText.ToString() }); } FText LongIntentNameFormat = MetaData->LongIntentNameFormat; if (LongIntentNameFormat.IsEmpty()) { LongIntentNameFormat = NSLOCTEXT("SequencerIKeyArea", "LongIntentNameFormat", "{ObjectBindingName}.{GroupName}.{DisplayName}"); } FormatArgs.Add(TEXT("IntentName"), FText::FromString(IntentName)); FString LongIntentName = FText::Format(LongIntentNameFormat, FormatArgs).ToString(); UE::MovieScene::CleanUpCurveEditorFormatStringInline(LongIntentName); CurveModel->SetShortDisplayName(DisplayText); CurveModel->SetLongDisplayName(LongDisplayName); CurveModel->SetIntentionName(IntentName); CurveModel->SetLongIntentionName(LongIntentName); CurveModel->SetChannelName(MetaData->Name); if (Color.IsSet()) { CurveModel->SetColor(Color.GetValue(),false); } //Use editor preference color if it's been set, this function is const so we can't set just Color optional const UCurveEditorSettings* Settings = GetDefault(); UObject* Object = nullptr; FString Name; CurveModel->GetCurveColorObjectAndName(&Object, Name); if (Object) { TOptional SettingColor = Settings->GetCustomColor(Object->GetClass(), Name); if (SettingColor.IsSet()) { CurveModel->SetColor(SettingColor.GetValue(),false); } } // Make sure the curve consistently has the same ID. // If the curve is added, removed, then added again to curve editor, systems will recognize this curve as the "same". // This is relevant e.g. for undo / redo. // You can undo / redo selecting keys: if undo past a transaction that called FCurveEditor::AddCurve, // redoing that transaction needs to add back the same curve ID so redoing the key selection also works. CurveModel->InitCurveId(*CurveModelID); } return CurveModel; } return nullptr; } bool IKeyArea::ShouldShowCurve() const { ISequencerChannelInterface* EditorInterface = FindChannelEditorInterface(); FMovieSceneChannel* Channel = ChannelHandle.Get(); UMovieSceneSection* OwningSection = GetOwningSection(); if (EditorInterface && Channel && OwningSection) { return EditorInterface->ShouldShowCurve_Raw(Channel, OwningSection); } return false; }