510 lines
15 KiB
C++
510 lines
15 KiB
C++
// 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<SWidget> 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<UDaySequenceEditorSettings>();
|
|
Settings->bAutoBindToPIE = !Settings->bAutoBindToPIE;
|
|
Settings->SaveConfig();
|
|
|
|
OnSetPlaybackContextEvent.ExecuteIfBound(nullptr);
|
|
}
|
|
|
|
bool IsAutoPIEChecked() const
|
|
{
|
|
return GetDefault<UDaySequenceEditorSettings>()->bAutoBindToPIE;
|
|
}
|
|
|
|
void ToggleAutoSimulate() const
|
|
{
|
|
UDaySequenceEditorSettings* Settings = GetMutableDefault<UDaySequenceEditorSettings>();
|
|
Settings->bAutoBindToSimulate = !Settings->bAutoBindToSimulate;
|
|
Settings->SaveConfig();
|
|
|
|
OnSetPlaybackContextEvent.ExecuteIfBound(nullptr);
|
|
}
|
|
|
|
bool IsAutoSimulateChecked() const
|
|
{
|
|
return GetDefault<UDaySequenceEditorSettings>()->bAutoBindToSimulate;
|
|
}
|
|
|
|
void OnSetPlaybackContext(TWeakObjectPtr<ADaySequenceActor> InContext)
|
|
{
|
|
if (ADaySequenceActor* NewContext = InContext.Get())
|
|
{
|
|
OnSetPlaybackContextEvent.ExecuteIfBound(NewContext);
|
|
}
|
|
}
|
|
|
|
bool IsCurrentPlaybackContext(TWeakObjectPtr<ADaySequenceActor> InContext)
|
|
{
|
|
ADaySequenceActor* Context = PlaybackContextAttribute.Get();
|
|
return (InContext == Context);
|
|
}
|
|
|
|
private:
|
|
TAttribute<UDaySequence*> OwnerAttribute;
|
|
TAttribute<ADaySequenceActor*> 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<ADaySequenceActor*>& OutActors)
|
|
{
|
|
ADaySequenceActor* Fallback = nullptr;
|
|
|
|
for (const ULevel* Level : InWorld->GetLevels())
|
|
{
|
|
for (AActor* Actor : Level->Actors)
|
|
{
|
|
ADaySequenceActor* DayActor = Cast<ADaySequenceActor>(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<SWidget> 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<UDaySequenceEditorSettings>();
|
|
IMovieSceneCaptureDialogModule* CaptureDialogModule = FModuleManager::GetModulePtr<IMovieSceneCaptureDialogModule>("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<IDaySequenceEditorModule>("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<ADaySequenceActor*> 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<ADaySequenceActor*> 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<SWidget> SDaySequenceContextPicker::BuildWorldPickerMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(true, nullptr);
|
|
|
|
const UDaySequence* DaySequence = OwnerAttribute.Get();
|
|
|
|
const UDaySequenceEditorSettings* Settings = GetDefault<UDaySequenceEditorSettings>();
|
|
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<ADaySequenceActor*> 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<ADaySequenceActor>(nullptr)),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(
|
|
this,
|
|
&SDaySequenceContextPicker::IsCurrentPlaybackContext,
|
|
MakeWeakObjectPtr<ADaySequenceActor>(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
|