// Copyright Epic Games, Inc. All Rights Reserved. #include "SceneCaptureDetails.h" #include "Components/SceneCaptureComponent.h" #include "Components/SceneCaptureComponent2D.h" #include "Components/SceneCaptureComponentCube.h" #include "Containers/Array.h" #include "CoreTypes.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "Fonts/SlateFontInfo.h" #include "GameFramework/Actor.h" #include "HAL/PlatformCrt.h" #include "IDetailGroup.h" #include "IRenderCaptureProvider.h" #include "Input/Reply.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "PropertyHandle.h" #include "RenderCaptureInterface.h" #include "ScopedTransaction.h" #include "ShowFlags.h" #include "SlotBase.h" #include "Styling/SlateTypes.h" #include "Templates/Casts.h" #include "Types/SlateEnums.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "SceneCaptureDetails" TSharedRef FSceneCaptureDetails::MakeInstance() { return MakeShareable( new FSceneCaptureDetails ); } namespace UE { namespace DetailsCustomization { namespace Private { // Used to sort the show flags inside of their categories in the order of the text that is actually displayed inline static bool SortAlphabeticallyByLocalizedText(const FString& ip1, const FString& ip2) { FText LocalizedText1; FEngineShowFlags::FindShowFlagDisplayName(ip1, LocalizedText1); FText LocalizedText2; FEngineShowFlags::FindShowFlagDisplayName(ip2, LocalizedText2); return LocalizedText1.ToString() < LocalizedText2.ToString(); } // Utility function to get a show flag state by name. static bool GetShowFlagState(const USceneCaptureComponent* SceneCaptureComponent, FStringView ShowFlagName) { if (!IsValid(SceneCaptureComponent)) { return false; } const int32 SettingIndex = SceneCaptureComponent->ShowFlags.FindIndexByName(ShowFlagName.GetData()); return SettingIndex != INDEX_NONE ? SceneCaptureComponent->ShowFlags.GetSingleFlag(SettingIndex) : false; } // Update archetype instances so that once the value matches the default, the instance override will be removed. This correctly mimics the behavior of other checkbox states. static void UpdateArchetypeChainShowFlagSettings(USceneCaptureComponent* SceneCaptureComponent, FStringView ShowFlagName, bool bEnabledState) { if (!IsValid(SceneCaptureComponent)) { return; } FProperty* ShowFlagsSettingsPropertyVariable = FindFieldChecked(USceneCaptureComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(USceneCaptureComponent, ShowFlagSettings)); FPropertyChangedEvent PropertyChangedEvent(ShowFlagsSettingsPropertyVariable); // Update archetype instances so that once the value matches the default, the instance override will be removed. // This correctly mimics the behavior of other checkbox states. TArray Instances; SceneCaptureComponent->GetArchetypeInstances(Instances); for (UObject* Instance : Instances) { if (USceneCaptureComponent* InstanceComponent = Cast(Instance)) { TArray InstanceShowFlagSettings = InstanceComponent->GetShowFlagSettings(); const int32 RemovedElements = InstanceShowFlagSettings.RemoveAll([ShowFlagName, bEnabledState](const FEngineShowFlagsSetting& InSetting) { return ShowFlagName.Equals(InSetting.ShowFlagName) && (bEnabledState == InSetting.Enabled); }); if (RemovedElements > 0) { InstanceComponent->PreEditChange(ShowFlagsSettingsPropertyVariable); InstanceComponent->SetShowFlagSettings(InstanceShowFlagSettings); InstanceComponent->PostEditChangeProperty(PropertyChangedEvent); } } } } } } } void FSceneCaptureDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { IDetailCategoryBuilder& SceneCaptureCategoryBuilder = DetailLayout.EditCategory("SceneCapture"); ShowFlagSettingsProperty = DetailLayout.GetProperty("ShowFlagSettings", USceneCaptureComponent::StaticClass()); check(ShowFlagSettingsProperty->IsValidHandle()); ShowFlagSettingsProperty->MarkHiddenByCustomization(); // Add all the properties that are there by default // (These would get added by default anyway, but we want to add them first so what we add next comes later in the list) TArray> SceneCaptureCategoryDefaultProperties; SceneCaptureCategoryBuilder.GetDefaultProperties(SceneCaptureCategoryDefaultProperties); for (TSharedRef Handle : SceneCaptureCategoryDefaultProperties) { if (Handle->GetProperty() != ShowFlagSettingsProperty->GetProperty()) { SceneCaptureCategoryBuilder.AddProperty(Handle); } } // Show flags that should be exposed for Scene Captures TArray ShowFlagsToAllowForCaptures; ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Atmosphere); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Cloud); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_BSP); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Decals); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Fog); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Landscape); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Particles); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_SkeletalMeshes); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_StaticMeshes); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Translucency); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Lighting); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_DeferredLighting); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_NaniteMeshes); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_InstancedStaticMeshes); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_InstancedFoliage); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_InstancedGrass); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Paper2DSprites); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_TextRender); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_AmbientOcclusion); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_DynamicShadows); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_SkyLighting); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_VolumetricFog); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_AmbientCubemap); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_DistanceFieldAO); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_LightFunctions); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_LightShafts); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_PostProcessing); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_ReflectionEnvironment); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_ScreenSpaceReflections); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_TexturedLightProfiles); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_AntiAliasing); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_TemporalAA); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_MotionBlur); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Bloom); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_LocalExposure); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_EyeAdaptation); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Game); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_ToneCurve); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_PathTracing); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_Tonemapper); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_PostProcessMaterial); ShowFlagsToAllowForCaptures.Add(FEngineShowFlags::EShowFlag::SF_AllowPrimitiveAlphaHoldout); // Create array of flag name strings for each group TArray< TArray > ShowFlagsByGroup; for (int32 GroupIndex = 0; GroupIndex < SFG_Max; ++GroupIndex) { ShowFlagsByGroup.Add(TArray()); } // Add the show flags we want to expose to their group's array for (FEngineShowFlags::EShowFlag AllowedFlag : ShowFlagsToAllowForCaptures) { FString FlagName; FlagName = FEngineShowFlags::FindNameByIndex(AllowedFlag); if (!FlagName.IsEmpty()) { EShowFlagGroup Group = FEngineShowFlags::FindShowFlagGroup(*FlagName); ShowFlagsByGroup[Group].Add(FlagName); } } // Sort the flags in their respective group alphabetically for (TArray& ShowFlagGroup : ShowFlagsByGroup) { ShowFlagGroup.Sort(UE::DetailsCustomization::Private::SortAlphabeticallyByLocalizedText); } // Add each group for (int32 GroupIndex = 0; GroupIndex < SFG_Max; ++GroupIndex) { // Don't add a group if there are no flags allowed for it if (ShowFlagsByGroup[GroupIndex].Num() >= 1) { FText GroupName; FText GroupTooltip; switch (GroupIndex) { case SFG_Normal: GroupName = LOCTEXT("CommonShowFlagHeader", "General Show Flags"); break; case SFG_Advanced: GroupName = LOCTEXT("AdvancedShowFlagsMenu", "Advanced Show Flags"); break; case SFG_PostProcess: GroupName = LOCTEXT("PostProcessShowFlagsMenu", "Post Processing Show Flags"); break; case SFG_Developer: GroupName = LOCTEXT("DeveloperShowFlagsMenu", "Developer Show Flags"); break; case SFG_Visualize: GroupName = LOCTEXT("VisualizeShowFlagsMenu", "Visualize Show Flags"); break; case SFG_LightTypes: GroupName = LOCTEXT("LightTypesShowFlagsMenu", "Light Types Show Flags"); break; case SFG_LightingComponents: GroupName = LOCTEXT("LightingComponentsShowFlagsMenu", "Lighting Components Show Flags"); break; case SFG_LightingFeatures: GroupName = LOCTEXT("LightingFeaturesShowFlagsMenu", "Lighting Features Show Flags"); break; case SFG_Lumen: GroupName = LOCTEXT("LumenFeaturesShowFlagsMenu", "Lumen Show Flags"); break; case SFG_Nanite: GroupName = LOCTEXT("NaniteFeaturesShowFlagsMenu", "Nanite Show Flags"); break; case SFG_CollisionModes: GroupName = LOCTEXT("CollisionModesShowFlagsMenu", "Collision Modes Show Flags"); break; case SFG_Hidden: case SFG_Transient: GroupName = LOCTEXT("HiddenShowFlagsMenu", "Hidden Show Flags"); break; default: // Should not get here unless a new group is added without being updated here GroupName = LOCTEXT("MiscFlagsMenu", "Misc Show Flags"); break; } FName GroupFName = FName(*(GroupName.ToString())); IDetailGroup& Group = SceneCaptureCategoryBuilder.AddGroup(GroupFName, GroupName, true); // Add each show flag for this group for (FString& FlagName : ShowFlagsByGroup[GroupIndex]) { bool bFlagHidden = false; FText LocalizedText; FEngineShowFlags::FindShowFlagDisplayName(FlagName, LocalizedText); Group.AddWidgetRow() .IsEnabled(true) .OverrideResetToDefault(FResetToDefaultOverride::Create(TAttribute::CreateSP(this, &FSceneCaptureDetails::GetShowFlagResetVisibility, FlagName), FSimpleDelegate::CreateSP(this, &FSceneCaptureDetails::OnResetShowFlag, FlagName))) .NameContent() [ SNew(STextBlock) .Text(LocalizedText) ] .ValueContent() [ SNew(SCheckBox) .OnCheckStateChanged(this, &FSceneCaptureDetails::OnShowFlagCheckStateChanged, FlagName) .IsChecked(this, &FSceneCaptureDetails::OnGetDisplayCheckState, FlagName) ] .FilterString(LocalizedText); } } } if (IRenderCaptureProvider::IsAvailable()) { IDetailCategoryBuilder& DebugCategoryBuilder = DetailLayout.EditCategory("Debug"); TArray> Objects; DetailLayout.GetObjectsBeingCustomized(Objects); const FText RenderCaptureText = LOCTEXT("RenderCaptureText", "Render Capture"); const FText RenderCaptureTooltip = LOCTEXT("RenderCaptureTooltip", "Triggers a render capture and opens it in the enabled render capture software (e.g. Render Doc, if RenderDocPlugin is enabled)"); DebugCategoryBuilder.AddCustomRow(RenderCaptureText).WholeRowContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() .Padding(5.0f, 0.0f, 0.0f, 0.0f) [ SNew(SButton) .ToolTipText(RenderCaptureTooltip) .IsEnabled_Lambda([Objects]() -> bool { for (TWeakObjectPtr Object : Objects) { if (Object.IsValid() && (Object->IsA() || Object->IsA())) { return true; } } return false; }) .OnClicked_Lambda([Objects]() -> FReply { for (TWeakObjectPtr Object : Objects) { if (USceneCaptureComponent* SceneCaptureComponent = Cast(Object.Get())) { RenderCaptureInterface::FScopedCapture RenderCapture(/*bEnable = */true, *FString::Format(TEXT("Scene Capture : {0}"), { SceneCaptureComponent->GetOwner()->GetActorLabel() })); if (USceneCaptureComponent2D* SceneCaptureComponent2D = Cast(SceneCaptureComponent)) { SceneCaptureComponent2D->CaptureScene(); } else if (USceneCaptureComponentCube* SceneCaptureComponentCube = Cast(SceneCaptureComponent)) { SceneCaptureComponentCube->CaptureScene(); } } } return FReply::Handled(); }) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(RenderCaptureText) ] ] ]; } } ECheckBoxState FSceneCaptureDetails::OnGetDisplayCheckState(FString ShowFlagName) const { TArray RawData; ShowFlagSettingsProperty->AccessRawData(RawData); TArray OuterObjects; ShowFlagSettingsProperty->GetOuterObjects(OuterObjects); ECheckBoxState ReturnState = ECheckBoxState::Unchecked; bool bReturnStateSet = false; for (int32 ObjectIdx = 0; ObjectIdx < RawData.Num(); ++ObjectIdx) { const void* Data = RawData[ObjectIdx]; // Data can be nullptr when the SceneCapture is removed while its details are visible if (!Data) { return ECheckBoxState::Unchecked; } const TArray& ShowFlagSettings = *reinterpret_cast*>(Data); const FEngineShowFlagsSetting* Setting = ShowFlagSettings.FindByPredicate([&ShowFlagName](const FEngineShowFlagsSetting& S) { return S.ShowFlagName == ShowFlagName; }); ECheckBoxState ThisObjectState = ECheckBoxState::Unchecked; if (Setting) { ThisObjectState = Setting->Enabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } else { const UObject* SceneComp = OuterObjects[ObjectIdx]; const USceneCaptureComponent* SceneCompArchetype = SceneComp ? Cast(SceneComp->GetArchetype()) : nullptr; const int32 SettingIndex = SceneCompArchetype ? SceneCompArchetype->ShowFlags.FindIndexByName(*ShowFlagName) : INDEX_NONE; if (SettingIndex != INDEX_NONE) { ThisObjectState = SceneCompArchetype->ShowFlags.GetSingleFlag(SettingIndex) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } if (bReturnStateSet) { if (ThisObjectState != ReturnState) { ReturnState = ECheckBoxState::Undetermined; break; } } else { ReturnState = ThisObjectState; bReturnStateSet = true; } } return ReturnState; } void FSceneCaptureDetails::OnShowFlagCheckStateChanged(ECheckBoxState InNewRadioState, FString FlagName) { if (InNewRadioState == ECheckBoxState::Undetermined) { return; } FScopedTransaction Transaction(LOCTEXT("ShowFlagCheckStateChanged", "Show Flag Check State Changed")); ShowFlagSettingsProperty->NotifyPreChange(); TArray RawData; ShowFlagSettingsProperty->AccessRawData(RawData); TArray OuterObjects; ShowFlagSettingsProperty->GetOuterObjects(OuterObjects); const bool bNewEnabledState = (InNewRadioState == ECheckBoxState::Checked) ? true : false; for (int32 ObjectIdx = 0; ObjectIdx < RawData.Num(); ++ObjectIdx) { void* Data = RawData[ObjectIdx]; check(Data); USceneCaptureComponent* SceneComp = Cast(OuterObjects[ObjectIdx]); const USceneCaptureComponent* SceneCompArchetype = SceneComp ? Cast(SceneComp->GetArchetype()) : nullptr; const bool bDefaultValue = UE::DetailsCustomization::Private::GetShowFlagState(SceneCompArchetype, FlagName); TArray& ShowFlagSettings = *reinterpret_cast*>(Data); if (bNewEnabledState == bDefaultValue) { // Just remove settings that are the same as defaults. This lets the flags return to their default state ShowFlagSettings.RemoveAll([&FlagName](const FEngineShowFlagsSetting& Setting) { return Setting.ShowFlagName == FlagName; }); } else { FEngineShowFlagsSetting* Setting = ShowFlagSettings.FindByPredicate([&FlagName](const FEngineShowFlagsSetting& S) { return S.ShowFlagName == FlagName; }); if (Setting) { // If the setting exists already for some reason, update it Setting->Enabled = bNewEnabledState; } else { // Otherwise create a new setting FEngineShowFlagsSetting NewFlagSetting; NewFlagSetting.ShowFlagName = FlagName; NewFlagSetting.Enabled = bNewEnabledState; ShowFlagSettings.Add(NewFlagSetting); } } UE::DetailsCustomization::Private::UpdateArchetypeChainShowFlagSettings(SceneComp, FlagName, bNewEnabledState); } ShowFlagSettingsProperty->NotifyPostChange(EPropertyChangeType::ValueSet); ShowFlagSettingsProperty->NotifyFinishedChangingProperties(); } bool FSceneCaptureDetails::GetShowFlagResetVisibility(FString ShowFlagName) const { TArray OuterObjects; ShowFlagSettingsProperty->GetOuterObjects(OuterObjects); for (UObject* OuterObject : OuterObjects) { if (const USceneCaptureComponent* SceneCaptureComponent = Cast(OuterObject)) { if (const USceneCaptureComponent* SceneCompArchetype = Cast(SceneCaptureComponent->GetArchetype())) { return UE::DetailsCustomization::Private::GetShowFlagState(SceneCaptureComponent, ShowFlagName) != UE::DetailsCustomization::Private::GetShowFlagState(SceneCompArchetype, ShowFlagName); } } } return false; } void FSceneCaptureDetails::OnResetShowFlag(FString ShowFlagName) { FScopedTransaction Transaction(LOCTEXT("OnResetShowFlag", "On Reset Show Flag")); FProperty* ShowFlagsSettingsPropertyVariable = FindFieldChecked(USceneCaptureComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(USceneCaptureComponent, ShowFlagSettings)); FPropertyChangedEvent PropertyChangedEvent(ShowFlagsSettingsPropertyVariable); ShowFlagSettingsProperty->NotifyPreChange(); TArray RawData; ShowFlagSettingsProperty->AccessRawData(RawData); TArray OuterObjects; ShowFlagSettingsProperty->GetOuterObjects(OuterObjects); for (int32 ObjectIdx = 0; ObjectIdx < RawData.Num(); ++ObjectIdx) { void* Data = RawData[ObjectIdx]; check(Data); if (USceneCaptureComponent* SceneCaptureComponent = Cast(OuterObjects[ObjectIdx])) { // Reset to archetype default TArray& ShowFlagSettings = *reinterpret_cast*>(Data); ShowFlagSettings.RemoveAll([&ShowFlagName](const FEngineShowFlagsSetting& InSetting) { return InSetting.ShowFlagName.Equals(ShowFlagName); }); const USceneCaptureComponent* SceneCompArchetype = Cast(SceneCaptureComponent->GetArchetype()); const bool bDefaultValue = UE::DetailsCustomization::Private::GetShowFlagState(SceneCompArchetype, ShowFlagName); UE::DetailsCustomization::Private::UpdateArchetypeChainShowFlagSettings(SceneCaptureComponent, ShowFlagName, bDefaultValue); } } ShowFlagSettingsProperty->NotifyPostChange(EPropertyChangeType::ValueSet); ShowFlagSettingsProperty->NotifyFinishedChangingProperties(); } #undef LOCTEXT_NAMESPACE