// Copyright 2019-Present LexLiu. All Rights Reserved. #include "LGUIPrefabSequenceEditorWidget.h" #include "PrefabAnimation/LGUIPrefabSequence.h" #include "ISequencer.h" #include "ISequencerModule.h" #include "LevelEditorSequencerIntegration.h" #include "SSCSEditor.h" #include "Styling/SlateIconFinder.h" #include "Kismet2/BlueprintEditorUtils.h" #include "EditorStyleSet.h" #include "EditorUndoClient.h" #include "Widgets/Images/SImage.h" #include "Editor.h" #include "ScopedTransaction.h" #include "Widgets/Docking/SDockTab.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Application/SlateApplication.h" #include "EngineUtils.h" #include "Utils/LGUIUtils.h" #include "LGUIEditorTools.h" #include "PrefabSystem/LGUIPrefabHelperObject.h" #include "PrefabAnimation/LGUIPrefabSequenceComponent.h" #include "Selection.h" #include "Core/Actor/UIBaseActor.h" #include "LevelEditor.h" #define LOCTEXT_NAMESPACE "LGUIPrefabSequenceEditorWidget" DECLARE_DELEGATE_OneParam(FPrefabAnimationOnComponentSelected, TSharedPtr); DECLARE_DELEGATE_RetVal_OneParam(bool, FPrefabAnimationIsComponentValid, UActorComponent*); #include "Core/ActorComponent/UIBatchMeshRenderable.h" #include "PrefabAnimation/MovieSceneLGUIMaterialTrack.h" class SLGUIPrefabSequenceEditorWidgetImpl : public SCompoundWidget, public FEditorUndoClient { public: bool bUpdatingSequencerSelection = false; SLATE_BEGIN_ARGS(SLGUIPrefabSequenceEditorWidgetImpl){} SLATE_END_ARGS(); void Close() { if (Sequencer.IsValid()) { Sequencer->SetShowCurveEditor(false); FLevelEditorSequencerIntegration::Get().RemoveSequencer(Sequencer.ToSharedRef()); Sequencer->Close(); Sequencer = nullptr; } GEditor->UnregisterForUndo(this); } ~SLGUIPrefabSequenceEditorWidgetImpl() { Close(); // Un-Register sequencer menu extenders. ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked("Sequencer"); SequencerModule.GetAddTrackMenuExtensibilityManager()->GetExtenderDelegates().RemoveAll([this](const FAssetEditorExtender& Extender) { return SequencerAddTrackExtenderHandle == Extender.GetHandle(); }); } TSharedRef SpawnCurveEditorTab(const FSpawnTabArgs&) { const FSlateIcon SequencerGraphIcon = FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCurveEditor.TabIcon"); auto Tab = SNew(SDockTab) .Label(NSLOCTEXT("Sequencer", "SequencerMainGraphEditorTitle", "Sequencer Curves")) [ SNullWidget::NullWidget ]; Tab->SetTabIcon(SequencerGraphIcon.GetIcon()); return Tab; } void Construct(const FArguments&, TWeakPtr InBlueprintEditor) { NoAnimationTextBlock = SNew(STextBlock) .TextStyle(FAppStyle::Get(), "UMGEditor.NoAnimationFont") .Text(LOCTEXT("NoAnimationSelected", "No Animation Selected")); ChildSlot [ SNew(SOverlay) + SOverlay::Slot() [ SAssignNew(Content, SBox) .MinDesiredHeight(200) ] +SOverlay::Slot() .HAlign(HAlign_Center) .VAlign(VAlign_Center) [ NoAnimationTextBlock.ToSharedRef() ] ]; GEditor->RegisterForUndo(this); ToolkitHost = FModuleManager::LoadModuleChecked("LevelEditor").GetFirstLevelEditor(); // Register sequencer menu extenders. ISequencerModule& SequencerModule = FModuleManager::Get().LoadModuleChecked("Sequencer"); { int32 NewIndex = SequencerModule.GetAddTrackMenuExtensibilityManager()->GetExtenderDelegates().Add( FAssetEditorExtender::CreateRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::GetAddTrackSequencerExtender)); SequencerAddTrackExtenderHandle = SequencerModule.GetAddTrackMenuExtensibilityManager()->GetExtenderDelegates()[NewIndex].GetHandle(); } } virtual void PostUndo(bool bSuccess) override { if (!GetLGUIPrefabSequence()) { Close(); } } FText GetDisplayLabel() const { ULGUIPrefabSequence* Sequence = WeakSequence.Get(); return Sequence ? Sequence->GetDisplayName() : LOCTEXT("DefaultSequencerLabel", "Sequencer"); } ULGUIPrefabSequence* GetLGUIPrefabSequence() const { return WeakSequence.Get(); } UObject* GetPlaybackContext() const { ULGUIPrefabSequence* LocalLGUIPrefabSequence = GetLGUIPrefabSequence(); if (LocalLGUIPrefabSequence) { auto Component = LocalLGUIPrefabSequence->GetTypedOuter(); return Component->GetOwner(); } return nullptr; } TArray GetEventContexts() const { TArray Contexts; if (auto* Context = GetPlaybackContext()) { Contexts.Add(Context); } return Contexts; } auto GetNullSequence() { static ULGUIPrefabSequence* NullSequence = nullptr; if (!NullSequence) { NullSequence = NewObject(GetTransientPackage(), NAME_None); NullSequence->AddToRoot(); NullSequence->GetMovieScene()->SetDisplayRate(FFrameRate(30, 1)); } return NullSequence; } void SetLGUIPrefabSequence(ULGUIPrefabSequence* NewSequence) { if (ULGUIPrefabSequence* OldSequence = WeakSequence.Get()) { if (OnSequenceChangedHandle.IsValid()) { OldSequence->OnSignatureChanged().Remove(OnSequenceChangedHandle); } } WeakSequence = NewSequence; if (NewSequence) { OnSequenceChangedHandle = NewSequence->OnSignatureChanged().AddSP(this, &SLGUIPrefabSequenceEditorWidgetImpl::OnSequenceChanged); } if (NewSequence == nullptr) { Content->SetEnabled(false); NoAnimationTextBlock->SetVisibility(EVisibility::Visible); } else { Content->SetEnabled(true); NoAnimationTextBlock->SetVisibility(EVisibility::Collapsed); } // If we already have a sequencer open, just assign the sequence if (Sequencer.IsValid() && NewSequence) { if (Sequencer->GetRootMovieSceneSequence() != NewSequence) { Sequencer->ResetToNewRootSequence(*NewSequence); } return; } // If we're setting the sequence to none, destroy sequencer if (!NewSequence) { if (Sequencer.IsValid()) { Sequencer->SetShowCurveEditor(false); FLevelEditorSequencerIntegration::Get().RemoveSequencer(Sequencer.ToSharedRef()); Sequencer->Close(); Sequencer = nullptr; } Content->SetContent(SNew(STextBlock).Text(LOCTEXT("NothingSelected", "Select a sequence"))); return; } // We need to initialize a new sequencer instance FSequencerInitParams SequencerInitParams; { TWeakObjectPtr LocalWeakSequence = NewSequence; SequencerInitParams.RootSequence = NewSequence ? NewSequence : GetNullSequence(); SequencerInitParams.EventContexts = TAttribute>(this, &SLGUIPrefabSequenceEditorWidgetImpl::GetEventContexts); SequencerInitParams.PlaybackContext = TAttribute(this, &SLGUIPrefabSequenceEditorWidgetImpl::GetPlaybackContext); TSharedRef AddMenuExtender = MakeShareable(new FExtender); AddMenuExtender->AddMenuExtension("AddTracks", EExtensionHook::Before, nullptr, FMenuExtensionDelegate::CreateRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::AddPossessMenuExtensions) ); SequencerInitParams.ViewParams.bReadOnly = !NewSequence->IsEditable(); SequencerInitParams.ViewParams.AddMenuExtender = AddMenuExtender; SequencerInitParams.ViewParams.UniqueName = "EmbeddedLGUIPrefabSequenceEditor"; SequencerInitParams.ViewParams.ScrubberStyle = ESequencerScrubberStyle::FrameBlock; SequencerInitParams.ViewParams.OnReceivedFocus.BindRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::OnSequencerReceivedFocus); SequencerInitParams.bEditWithinLevelEditor = false; SequencerInitParams.ToolkitHost = ToolkitHost; SequencerInitParams.HostCapabilities.bSupportsCurveEditor = true; } Sequencer = FModuleManager::LoadModuleChecked("Sequencer").CreateSequencer(SequencerInitParams); Content->SetContent(Sequencer->GetSequencerWidget()); Sequencer->GetSelectionChangedObjectGuids().AddSP(this, &SLGUIPrefabSequenceEditorWidgetImpl::SyncSelectedWidgetsWithSequencerSelection); Sequencer->OnMovieSceneBindingsChanged().AddLambda([=, this]() { if (!WeakSequence.IsValid())return; if (!IsValid(WeakSequence->GetMovieScene()))return; auto& Bindings = WeakSequence->GetMovieScene()->GetBindings(); for (auto& BindingItem : Bindings) { auto ObjectArray = Sequencer->FindObjectsInCurrentSequence(BindingItem.GetObjectGuid()); if (ObjectArray.Num() > 0) { if (auto Actor = Cast(ObjectArray[0])) { WeakSequence->GetMovieScene()->SetObjectDisplayName(BindingItem.GetObjectGuid(), FText::FromString(Actor->GetActorLabel())); } else if (auto Comp = Cast(ObjectArray[0])) { if (auto CompActor = Comp->GetOwner()) { WeakSequence->GetMovieScene()->SetObjectDisplayName(BindingItem.GetObjectGuid(), FText::FromString(CompActor->GetActorLabel())); } } } } }); FLevelEditorSequencerIntegrationOptions Options; Options.bRequiresLevelEvents = false; Options.bRequiresActorEvents = true; Options.bForceRefreshDetails = false; FLevelEditorSequencerIntegration::Get().AddSequencer(Sequencer.ToSharedRef(), Options); } // sequence select widget handler void SyncSelectedWidgetsWithSequencerSelection(TArray ObjectGuids) { if (Sequencer == nullptr || bUpdatingSequencerSelection) { return; } //UE_LOG(LGUI, Log, TEXT("SyncSelectedWidgetsWithSequencerSelection, ObjectGuids.Num()=%d"), ObjectGuids.Num()); TGuardValue Guard(bUpdatingSequencerSelection, true); UMovieSceneSequence* AnimationSequence = Sequencer->GetFocusedMovieSceneSequence(); UObject* BindingContext = WeakSequence.Get(); TSet SequencerSelectedWidgets; for (FGuid Guid : ObjectGuids) { TArray> BoundObjects; AnimationSequence->LocateBoundObjects(Guid, BindingContext, MovieSceneHelpers::CreateTransientSharedPlaybackState(BindingContext->GetWorld(), Cast(BindingContext)), BoundObjects); if (BoundObjects.Num() == 0) { continue; } else { AUIBaseActor* BoundWidget = Cast(BoundObjects[0]); if (BoundWidget) { SequencerSelectedWidgets.Add(BoundWidget); } } } if (SequencerSelectedWidgets.Num() != 0) { AUIBaseActor* SelectedActor = *SequencerSelectedWidgets.begin(); // Sync Selection GEditor->SelectNone(false, true, false); GEditor->SelectActor(SelectedActor, true, true, true); } } void OnSequencerReceivedFocus() { if (Sequencer.IsValid()) { FLevelEditorSequencerIntegration::Get().OnSequencerReceivedFocus(Sequencer.ToSharedRef()); } } void OnSelectionUpdated(TSharedPtr SelectedNode) { if (SelectedNode->GetNodeType() != FSCSEditorTreeNode::ComponentNode) { return; } UActorComponent* EditingComponent = nullptr; if (AActor* Actor = GetSelectedActor()) { EditingComponent = SelectedNode->FindComponentInstanceInActor(Actor); } if (EditingComponent) { const FScopedTransaction Transaction(LOCTEXT("AddComponentToSequencer", "Add component to Sequencer")); Sequencer->GetHandleToObject(EditingComponent, true); } FSlateApplication::Get().DismissAllMenus(); } void AddPossessMenuExtensions(FMenuBuilder& MenuBuilder) { if (!WeakSequence.IsValid())return; Sequencer->State.ClearObjectCaches(*Sequencer); TSet AllBoundObjects; UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index) { FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index); for (TWeakObjectPtr<> WeakObject : Sequencer->FindBoundObjects(Possessable.GetGuid(), Sequencer->GetFocusedTemplateID())) { if (UObject* Object = WeakObject.Get()) { AllBoundObjects.Add(Object); } } } //actor menu { TArray ValidActorArray; if (auto Actor = WeakSequence->GetTypedOuter()) { TArray AllChildrenActors; LGUIUtils::CollectChildrenActors(Actor, AllChildrenActors); for (auto ActorItem : AllChildrenActors) { if (!AllBoundObjects.Contains(ActorItem)) { ValidActorArray.Add(ActorItem); } } } MenuBuilder.AddSubMenu( LOCTEXT("AddActor_Label", "Actor"), LOCTEXT("AddActor_Tooltip", "Add a binding to one of actor and allow it to be animated by Sequencer"), FNewMenuDelegate::CreateRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::AddPossessActorMenuExtensions, ValidActorArray), false, FSlateIcon() ); } //component menu if (auto Actor = GetSelectedActor()) { TArray AllCompArray; Actor->GetComponents(AllCompArray); TArray ValidCompArray; for (auto Comp : AllCompArray) { if (Comp->HasAnyFlags(EObjectFlags::RF_Transient))continue; if (!AllBoundObjects.Contains(Comp))//already bounded { ValidCompArray.Add(Comp); } } MenuBuilder.AddSubMenu( LOCTEXT("AddComponent_Label", "Component"), LOCTEXT("AddComponent_ToolTip", "Add a binding to one of this actor's components and allow it to be animated by Sequencer"), FNewMenuDelegate::CreateRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::AddPossessComponentMenuExtensions, ValidCompArray), false, FSlateIcon() ); } } void AddPossessActorMenuExtensions(FMenuBuilder& MenuBuilder, TArray InActorArray) { for (auto Actor : InActorArray) { if (IsValid(Actor)) { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("ActorLabelFormat", "{0} ({1})"), FText::FromString(Actor->GetActorLabel()), FText::FromString(Actor->GetClass()->GetName())), FText::FromString(Actor->GetName()), FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([=, this]() { const FScopedTransaction Transaction(LOCTEXT("AddActorToSequencer", "Add actor to Sequencer")); Sequencer->GetHandleToObject(Actor, true); })) ); } } } void AddPossessComponentMenuExtensions(FMenuBuilder& MenuBuilder, TArray InCompArray) { for (auto Comp : InCompArray) { if (IsValid(Comp)) { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("ComponentLabelFormat", "{0} ({1})"), FText::FromString(Comp->GetName()), FText::FromString(Comp->GetClass()->GetName())), FText::FromString(Comp->GetName()), FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([=, this]() { const FScopedTransaction Transaction(LOCTEXT("AddComponentToSequencer", "Add component to Sequencer")); Sequencer->GetHandleToObject(Comp, true); })) ); } } } AActor* GetSelectedActor()const { TArray> SelectedActorArray; if (WeakSequence.IsValid()) { TArray SelectedObjects; Sequencer->GetSelectedObjects(SelectedObjects); for (auto GuidItem : SelectedObjects) { TArray> BoundObjects; WeakSequence->LocateBoundObjects(GuidItem, nullptr, BoundObjects); for (auto Obj : BoundObjects) { if (auto Actor = Cast(Obj)) { SelectedActorArray.Add(Actor); } } } } return SelectedActorArray.Num() == 1 ? SelectedActorArray[0] : nullptr; } void OnSequenceChanged() { ULGUIPrefabSequence* LGUIPrefabSequence = WeakSequence.Get(); auto Actor = WeakSequence.IsValid() ? WeakSequence->GetTypedOuter() : nullptr; if (Actor) { if (auto PrefabHelperObject = LGUIEditorTools::GetPrefabHelperObject_WhichManageThisActor(Actor)) { PrefabHelperObject->SetAnythingDirty(); } } } private: TSharedRef GetAddTrackSequencerExtender(const TSharedRef CommandList, const TArray ContextSensitiveObjects) { TSharedRef AddTrackMenuExtender(new FExtender()); AddTrackMenuExtender->AddMenuExtension( SequencerMenuExtensionPoints::AddTrackMenu_PropertiesSection, EExtensionHook::Before, CommandList, FMenuExtensionDelegate::CreateRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::ExtendSequencerAddTrackMenu, ContextSensitiveObjects)); return AddTrackMenuExtender; } void ExtendSequencerAddTrackMenu(FMenuBuilder& AddTrackMenuBuilder, const TArray ContextObjects) { if (ContextObjects.Num() == 1) { auto Renderable = Cast(ContextObjects[0]); if (Renderable != nullptr) { if (Renderable != nullptr && Renderable->GetCustomUIMaterial() != nullptr) { auto MaterialProperty = Renderable->GetClass()->FindPropertyByName(UUIBatchMeshRenderable::GetCustomUIMaterialPropertyName()); AddTrackMenuBuilder.BeginSection("Materials", LOCTEXT("MaterialsSection", "Materials")); { FText DisplayNameText = MaterialProperty->GetDisplayNameText(); FUIAction AddMaterialAction(FExecuteAction::CreateRaw(this, &SLGUIPrefabSequenceEditorWidgetImpl::AddMaterialTrack, Renderable, MaterialProperty, DisplayNameText)); FText AddMaterialLabel = DisplayNameText; FText AddMaterialToolTip = FText::Format(LOCTEXT("MaterialToolTipFormat", "Add a material track for the {0} property."), DisplayNameText); AddTrackMenuBuilder.AddMenuEntry(AddMaterialLabel, AddMaterialToolTip, FSlateIcon(), AddMaterialAction); } AddTrackMenuBuilder.EndSection(); } } } } void AddMaterialTrack(UUIBatchMeshRenderable* Renderable, FProperty* MaterialProperty, FText MaterialPropertyDisplayName) { FGuid WidgetHandle = Sequencer->GetHandleToObject(Renderable); if (WidgetHandle.IsValid()) { UMovieScene* MovieScene = Sequencer->GetFocusedMovieSceneSequence()->GetMovieScene(); if (MovieScene->IsReadOnly()) { return; } FName MaterialPropertyNamePath = MaterialProperty->GetFName(); if (MovieScene->FindTrack(UMovieSceneLGUIMaterialTrack::StaticClass(), WidgetHandle, MaterialPropertyNamePath) == nullptr) { const FScopedTransaction Transaction(LOCTEXT("AddWidgetMaterialTrack", "Add widget material track")); MovieScene->Modify(); auto NewTrack = Cast(MovieScene->AddTrack(UMovieSceneLGUIMaterialTrack::StaticClass(), WidgetHandle)); NewTrack->Modify(); NewTrack->SetPropertyName(MaterialPropertyNamePath); NewTrack->SetDisplayName(FText::Format(LOCTEXT("TrackDisplayNameFormat", "{0}"), MaterialPropertyDisplayName)); Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); } } } private: TWeakObjectPtr WeakSequence; TSharedPtr Content; TSharedPtr Sequencer; FDelegateHandle OnSequenceChangedHandle; TSharedPtr NoAnimationTextBlock; /** The asset editor that created this Sequencer if any */ TSharedPtr ToolkitHost; FDelegateHandle SequencerAddTrackExtenderHandle; }; void SLGUIPrefabSequenceEditorWidget::Construct(const FArguments&, TWeakPtr InBlueprintEditor) { ChildSlot [ SAssignNew(Impl, SLGUIPrefabSequenceEditorWidgetImpl, InBlueprintEditor) ]; } FText SLGUIPrefabSequenceEditorWidget::GetDisplayLabel() const { return Impl.Pin()->GetDisplayLabel(); } void SLGUIPrefabSequenceEditorWidget::AssignSequence(ULGUIPrefabSequence* NewLGUIPrefabSequence) { Impl.Pin()->SetLGUIPrefabSequence(NewLGUIPrefabSequence); } ULGUIPrefabSequence* SLGUIPrefabSequenceEditorWidget::GetSequence() const { return Impl.Pin()->GetLGUIPrefabSequence(); } #undef LOCTEXT_NAMESPACE