// Copyright Epic Games, Inc. All Rights Reserved. #include "DaySequencePlaybackContext.h" #include "DaySequenceActor.h" #include "DaySequenceEditorSettings.h" #include "DaySequence.h" #include "Engine/Level.h" #include "IDaySequenceEditorModule.h" #include "Engine/NetDriver.h" #include "Engine/World.h" #include "Delegates/Delegate.h" #include "Editor.h" #include "Engine/Engine.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "MovieSceneCaptureDialogModule.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/SCompoundWidget.h" #include "Styling/AppStyle.h" #define LOCTEXT_NAMESPACE "DaySequencePlaybackContext" class SDaySequenceContextPicker : public SCompoundWidget { public: DECLARE_DELEGATE_OneParam(FOnSetPlaybackContext, ADaySequenceActor*); SLATE_BEGIN_ARGS(SDaySequenceContextPicker){} /** Attribute for retrieving the bound Day sequence */ SLATE_ATTRIBUTE(UDaySequence*, Owner) /** Attribute for retrieving the current context */ SLATE_ATTRIBUTE(ADaySequenceActor*, OnGetPlaybackContext) /** Called when the user explicitly chooses a new context */ SLATE_EVENT(FOnSetPlaybackContext, OnSetPlaybackContext) SLATE_END_ARGS() void Construct(const FArguments& InArgs); private: TSharedRef BuildWorldPickerMenu(); static FText GetContextDescription(const ADaySequenceActor* Context); static FText GetWorldDescription(const UWorld* World); FText GetCurrentContextText() const { const ADaySequenceActor* CurrentContext = PlaybackContextAttribute.Get(); return GetContextDescription(CurrentContext); } const FSlateBrush* GetBorderBrush() const { const ADaySequenceActor* CurrentContext = PlaybackContextAttribute.Get(); if (CurrentContext) { UWorld* CurrentWorld = CurrentContext->GetWorld(); check(CurrentWorld); if (CurrentWorld->WorldType == EWorldType::PIE) { return GEditor->bIsSimulatingInEditor ? FAppStyle::GetBrush("LevelViewport.StartingSimulateBorder") : FAppStyle::GetBrush("LevelViewport.StartingPlayInEditorBorder"); } } return FStyleDefaults::GetNoBrush(); } void ToggleAutoPIE() const { UDaySequenceEditorSettings* Settings = GetMutableDefault(); Settings->bAutoBindToPIE = !Settings->bAutoBindToPIE; Settings->SaveConfig(); OnSetPlaybackContextEvent.ExecuteIfBound(nullptr); } bool IsAutoPIEChecked() const { return GetDefault()->bAutoBindToPIE; } void ToggleAutoSimulate() const { UDaySequenceEditorSettings* Settings = GetMutableDefault(); Settings->bAutoBindToSimulate = !Settings->bAutoBindToSimulate; Settings->SaveConfig(); OnSetPlaybackContextEvent.ExecuteIfBound(nullptr); } bool IsAutoSimulateChecked() const { return GetDefault()->bAutoBindToSimulate; } void OnSetPlaybackContext(TWeakObjectPtr InContext) { if (ADaySequenceActor* NewContext = InContext.Get()) { OnSetPlaybackContextEvent.ExecuteIfBound(NewContext); } } bool IsCurrentPlaybackContext(TWeakObjectPtr InContext) { ADaySequenceActor* Context = PlaybackContextAttribute.Get(); return (InContext == Context); } private: TAttribute OwnerAttribute; TAttribute PlaybackContextAttribute; FOnSetPlaybackContext OnSetPlaybackContextEvent; }; namespace UE { namespace MovieScene { /** * Finds all Day sequence actors in the given world, and return those that point to the given sequence, or any day sequence actor as a fallback */ static void FindDaySequenceActors(const UWorld* InWorld, const UDaySequence* InDaySequence, TArray& OutActors) { ADaySequenceActor* Fallback = nullptr; for (const ULevel* Level : InWorld->GetLevels()) { for (AActor* Actor : Level->Actors) { ADaySequenceActor* DayActor = Cast(Actor); if (!DayActor) { continue; } Fallback = DayActor; if (DayActor->GetRootSequence() == InDaySequence || DayActor->ContainsDaySequence(InDaySequence)) { OutActors.Add(DayActor); } } } if (OutActors.Num() == 0 && Fallback != nullptr) { OutActors.Add(Fallback); } } } } FDaySequencePlaybackContext::FDaySequencePlaybackContext(UDaySequence* InDaySequence) : DaySequence(InDaySequence) { FEditorDelegates::MapChange.AddRaw(this, &FDaySequencePlaybackContext::OnMapChange); FEditorDelegates::PreBeginPIE.AddRaw(this, &FDaySequencePlaybackContext::OnPieEvent); FEditorDelegates::BeginPIE.AddRaw(this, &FDaySequencePlaybackContext::OnPieEvent); FEditorDelegates::PostPIEStarted.AddRaw(this, &FDaySequencePlaybackContext::OnPieEvent); FEditorDelegates::PrePIEEnded.AddRaw(this, &FDaySequencePlaybackContext::OnPieEvent); FEditorDelegates::EndPIE.AddRaw(this, &FDaySequencePlaybackContext::OnPieEvent); if (GEngine) { GEngine->OnWorldAdded().AddRaw(this, &FDaySequencePlaybackContext::OnWorldListChanged); GEngine->OnWorldDestroyed().AddRaw(this, &FDaySequencePlaybackContext::OnWorldListChanged); } } FDaySequencePlaybackContext::~FDaySequencePlaybackContext() { FEditorDelegates::MapChange.RemoveAll(this); FEditorDelegates::PreBeginPIE.RemoveAll(this); FEditorDelegates::BeginPIE.RemoveAll(this); FEditorDelegates::PostPIEStarted.RemoveAll(this); FEditorDelegates::PrePIEEnded.RemoveAll(this); FEditorDelegates::EndPIE.RemoveAll(this); if (GEngine) { GEngine->OnWorldAdded().RemoveAll(this); GEngine->OnWorldDestroyed().RemoveAll(this); } } void FDaySequencePlaybackContext::OnPieEvent(bool) { WeakCurrentContext = nullptr; } void FDaySequencePlaybackContext::OnMapChange(uint32) { WeakCurrentContext = nullptr; } void FDaySequencePlaybackContext::OnWorldListChanged(UWorld*) { WeakCurrentContext = nullptr; } UDaySequence* FDaySequencePlaybackContext::GetDaySequence() const { return DaySequence.Get(); } ADaySequenceActor* FDaySequencePlaybackContext::GetPlaybackContext() const { UpdateCachedContext(); return WeakCurrentContext.Get(); } UObject* FDaySequencePlaybackContext::GetPlaybackContextAsObject() const { return GetPlaybackContext(); } ADaySequenceActor* FDaySequencePlaybackContext::GetPlaybackClient() const { UpdateCachedContext(); return WeakCurrentContext.Get(); } IMovieScenePlaybackClient* FDaySequencePlaybackContext::GetPlaybackClientAsInterface() const { return GetPlaybackClient(); } void FDaySequencePlaybackContext::OverrideWith(ADaySequenceActor* InNewContext) { // InNewContext may be null to force an auto update WeakCurrentContext = InNewContext; } TSharedRef FDaySequencePlaybackContext::BuildWorldPickerCombo() { return SNew(SDaySequenceContextPicker) .Owner(this, &FDaySequencePlaybackContext::GetDaySequence) .OnGetPlaybackContext(this, &FDaySequencePlaybackContext::GetPlaybackContext) .OnSetPlaybackContext(this, &FDaySequencePlaybackContext::OverrideWith); } ADaySequenceActor* FDaySequencePlaybackContext::ComputePlaybackContext(const UDaySequence* InDaySequence) { const UDaySequenceEditorSettings* Settings = GetDefault(); IMovieSceneCaptureDialogModule* CaptureDialogModule = FModuleManager::GetModulePtr("MovieSceneCaptureDialog"); // Some plugins may not want us to automatically attempt to bind to the world where it doesn't make sense, // such as movie rendering. bool bAllowPlaybackContextBinding = true; IDaySequenceEditorModule* DaySequenceEditorModule = FModuleManager::GetModulePtr("DaySequenceEditor"); if (DaySequenceEditorModule) { DaySequenceEditorModule->OnComputePlaybackContext().Broadcast(bAllowPlaybackContextBinding); } UWorld* RecordingWorld = CaptureDialogModule ? CaptureDialogModule->GetCurrentlyRecordingWorld() : nullptr; // Only allow PIE and Simulate worlds if the settings allow them const bool bIsSimulatingInEditor = GEditor && GEditor->bIsSimulatingInEditor; const bool bIsPIEValid = (!bIsSimulatingInEditor && Settings->bAutoBindToPIE) || ( bIsSimulatingInEditor && Settings->bAutoBindToSimulate); UWorld* EditorWorld = nullptr; // Return PIE worlds if there are any for (const FWorldContext& Context : GEngine->GetWorldContexts()) { if (Context.WorldType == EWorldType::PIE) { UWorld* ThisWorld = Context.World(); const bool bIsServerWorld = (ThisWorld && ThisWorld->GetNetDriver() && ThisWorld->GetNetDriver()->IsServer()); if (bIsPIEValid && bAllowPlaybackContextBinding && RecordingWorld != ThisWorld && !bIsServerWorld) { TArray DaySequenceActors; UE::MovieScene::FindDaySequenceActors(ThisWorld, InDaySequence, DaySequenceActors); for (ADaySequenceActor* DaySequenceActor : DaySequenceActors) { return DaySequenceActor; } } } else if (Context.WorldType == EWorldType::Editor) { EditorWorld = Context.World(); } } if (ensure(EditorWorld)) { TArray DaySequenceActors; UE::MovieScene::FindDaySequenceActors(EditorWorld, InDaySequence, DaySequenceActors); for (ADaySequenceActor* DaySequenceActor : DaySequenceActors) { return DaySequenceActor; } } return nullptr; } void FDaySequencePlaybackContext::UpdateCachedContext() const { if (WeakCurrentContext.Get() != nullptr) { return; } WeakCurrentContext = ComputePlaybackContext(DaySequence.Get()); } void SDaySequenceContextPicker::Construct(const FArguments& InArgs) { OwnerAttribute = InArgs._Owner; PlaybackContextAttribute = InArgs._OnGetPlaybackContext; OnSetPlaybackContextEvent = InArgs._OnSetPlaybackContext; check(OwnerAttribute.IsSet()); check(PlaybackContextAttribute.IsSet()); check(OnSetPlaybackContextEvent.IsBound()); ChildSlot .Padding(0.0f) [ SNew(SBorder) .BorderImage(this, &SDaySequenceContextPicker::GetBorderBrush) .Padding(FMargin(4.f, 0.f)) [ SNew(SComboButton) .ContentPadding(0) .ForegroundColor(FSlateColor::UseForeground()) .ComboButtonStyle(FAppStyle::Get(), "SimpleComboButton") .OnGetMenuContent(this, &SDaySequenceContextPicker::BuildWorldPickerMenu) .ToolTipText(FText::Format(LOCTEXT("WorldPickerTextFomrat", "'{0}': The actor to use for previewing the effects of this day sequence."), GetCurrentContextText())) .ButtonContent() [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FAppStyle::Get().GetBrush("Icons.World")) ] ] ]; } FText SDaySequenceContextPicker::GetContextDescription(const ADaySequenceActor* Context) { if (!Context) { return LOCTEXT("InvalidPlaybackContext", "<< invalid >>"); } UWorld* World = Context->GetWorld(); const FText WorldDescription = GetWorldDescription(World); return FText::Format( LOCTEXT("PlaybackContextDescription", "{0} ({1})"), FText::FromString(Context->GetName()), WorldDescription ); } FText SDaySequenceContextPicker::GetWorldDescription(const UWorld* World) { FText PostFix; if (World->WorldType == EWorldType::PIE) { switch(World->GetNetMode()) { case NM_Client: PostFix = FText::Format(LOCTEXT("ClientPostfixFormat", " (Client {0})"), FText::AsNumber(World->GetOutermost()->GetPIEInstanceID() - 1)); break; case NM_DedicatedServer: case NM_ListenServer: PostFix = LOCTEXT("ServerPostfix", " (Server)"); break; case NM_Standalone: PostFix = GEditor->bIsSimulatingInEditor ? LOCTEXT("SimulateInEditorPostfix", " (Simulate)") : LOCTEXT("PlayInEditorPostfix", " (PIE)"); break; default: break; } } else if (World->WorldType == EWorldType::Editor) { PostFix = LOCTEXT("EditorPostfix", " (Editor)"); } return FText::Format(LOCTEXT("WorldFormat", "{0}{1}"), FText::FromString(World->GetFName().GetPlainNameString()), PostFix); } TSharedRef SDaySequenceContextPicker::BuildWorldPickerMenu() { FMenuBuilder MenuBuilder(true, nullptr); const UDaySequence* DaySequence = OwnerAttribute.Get(); const UDaySequenceEditorSettings* Settings = GetDefault(); MenuBuilder.BeginSection(NAME_None, LOCTEXT("ActorsHeader", "Actors")); { for (const FWorldContext& Context : GEngine->GetWorldContexts()) { UWorld* World = Context.World(); if (World == nullptr || (Context.WorldType != EWorldType::PIE && Context.WorldType != EWorldType::Editor)) { continue; } bool bFoundActors = false; if (DaySequence) { TArray DaySequenceActors; UE::MovieScene::FindDaySequenceActors(World, DaySequence, DaySequenceActors); bFoundActors = DaySequenceActors.Num() > 0; for (ADaySequenceActor* DaySequenceActor : DaySequenceActors) { MenuBuilder.AddMenuEntry( GetContextDescription(DaySequenceActor), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &SDaySequenceContextPicker::OnSetPlaybackContext, MakeWeakObjectPtr(DaySequenceActor)), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SDaySequenceContextPicker::IsCurrentPlaybackContext, MakeWeakObjectPtr(DaySequenceActor)) ), NAME_None, EUserInterfaceActionType::RadioButton ); } } if (!bFoundActors) { MenuBuilder.AddMenuEntry( GetContextDescription(nullptr), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &SDaySequenceContextPicker::OnSetPlaybackContext, MakeWeakObjectPtr(nullptr)), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SDaySequenceContextPicker::IsCurrentPlaybackContext, MakeWeakObjectPtr(nullptr)) ), NAME_None, EUserInterfaceActionType::RadioButton ); } } } MenuBuilder.EndSection(); MenuBuilder.BeginSection(NAME_None, LOCTEXT("OptionsHeader", "Options")); { MenuBuilder.AddMenuEntry( LOCTEXT("AutoBindPIE_Label", "Auto Bind to PIE"), LOCTEXT("AutoBindPIE_Tip", "Automatically binds an active Sequencer window to the current PIE world, if available."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SDaySequenceContextPicker::ToggleAutoPIE), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SDaySequenceContextPicker::IsAutoPIEChecked) ), NAME_None, EUserInterfaceActionType::ToggleButton ); MenuBuilder.AddMenuEntry( LOCTEXT("AutoBindSimulate_Label", "Auto Bind to Simulate"), LOCTEXT("AutoBindSimulate_Tip", "Automatically binds an active Sequencer window to the current Simulate world, if available."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SDaySequenceContextPicker::ToggleAutoSimulate), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SDaySequenceContextPicker::IsAutoSimulateChecked) ), NAME_None, EUserInterfaceActionType::ToggleButton ); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } #undef LOCTEXT_NAMESPACE