Files
UnrealEngineUWP/Engine/Source/Editor/DetailCustomizations/Private/WorldSettingsDetails.cpp
robert manuszewski d1443992e1 Deprecating ANY_PACKAGE.
This change consists of multiple changes:

Core:
- Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject)
- Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter
- Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses
- Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names
- Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used  in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed.
- Added static UClass::TryConvertShortNameToPathName utility function
- Added static UClass::TryFixShortClassNameExportPath utility function
- Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass')
- All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath
- Added a new startup test that checks for short type names in UClass/FProperty MetaData values

AssetRegistry:
- Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath
- Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names
- This also applies to a few other modules' APIs to match AssetRegistry changes

Everything else:
- Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input)
- Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName()
- Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names

#jira UE-99463
#rb many.people
[FYI] Marcus.Wassmer
#preflight 629248ec2256738f75de9b32

#codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786

#ROBOMERGE-OWNER: robert.manuszewski
#ROBOMERGE-AUTHOR: robert.manuszewski
#ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246)

[CL 20448496 by robert manuszewski in ue5-main branch]
2022-06-01 03:46:59 -04:00

503 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WorldSettingsDetails.h"
#include "Framework/Commands/UIAction.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Misc/MessageDialog.h"
#include "GameFramework/Actor.h"
#include "ScopedTransaction.h"
#include "Editor.h"
#include "Engine/Texture2D.h"
#include "Misc/MessageDialog.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "DetailCategoryBuilder.h"
#include "GameModeInfoCustomizer.h"
#include "Settings/EditorExperimentalSettings.h"
#include "GameFramework/WorldSettings.h"
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/WorldPartitionSubsystem.h"
#include "ScopedTransaction.h"
#include "ActorFolder.h"
#define LOCTEXT_NAMESPACE "WorldSettingsDetails"
FWorldSettingsDetails::~FWorldSettingsDetails()
{
}
/* IDetailCustomization overrides
*****************************************************************************/
void FWorldSettingsDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder )
{
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("GameMode");
CustomizeGameInfoProperty("DefaultGameMode", DetailBuilder, Category);
AddLightmapCustomization(DetailBuilder);
AddWorldCustomization(DetailBuilder);
DetailBuilder.HideProperty(AActor::GetHiddenPropertyName(), AActor::StaticClass());
}
/* FWorldSettingsDetails implementation
*****************************************************************************/
void FWorldSettingsDetails::CustomizeGameInfoProperty( const FName& PropertyName, IDetailLayoutBuilder& DetailBuilder, IDetailCategoryBuilder& CategoryBuilder )
{
// Get the object that we are viewing details of. Expect to only edit one WorldSettings object at a time!
TArray< TWeakObjectPtr<UObject> > ObjectsCustomized;
DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized);
UObject* ObjectCustomized = (ObjectsCustomized.Num() > 0) ? ObjectsCustomized[0].Get() : NULL;
// Allocate customizer object
GameInfoModeCustomizer = MakeShareable(new FGameModeInfoCustomizer(ObjectCustomized, PropertyName));
// Then use it to customize
GameInfoModeCustomizer->CustomizeGameModeSetting(DetailBuilder, CategoryBuilder);
}
void FWorldSettingsDetails::AddLightmapCustomization( IDetailLayoutBuilder& DetailBuilder )
{
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("Lightmass");
TSharedRef<FLightmapCustomNodeBuilder> LightMapGroupBuilder = MakeShareable(new FLightmapCustomNodeBuilder(DetailBuilder.GetThumbnailPool()));
const bool bForAdvanced = true;
Category.AddCustomBuilder(LightMapGroupBuilder, bForAdvanced);
}
void FWorldSettingsDetails::AddWorldCustomization(IDetailLayoutBuilder& DetailBuilder)
{
TArray<TWeakObjectPtr<UObject>> CustomizedObjects;
DetailBuilder.GetObjectsBeingCustomized(CustomizedObjects);
ULevel* CustomizedLevel = nullptr;
if (CustomizedObjects.Num() > 0)
{
if (AWorldSettings* WorldSettings = Cast<AWorldSettings>(CustomizedObjects[0]))
{
CustomizedLevel = WorldSettings->GetLevel();
SelectedWorldSettings = WorldSettings;
}
}
// Hide some of the WorldPartition properties found in AActor
TArray<TSharedRef<IPropertyHandle>> PropertiesToHide;
PropertiesToHide.Add(DetailBuilder.GetProperty(AActor::GetRuntimeGridPropertyName(), AActor::StaticClass()));
PropertiesToHide.Add(DetailBuilder.GetProperty(AActor::GetIsSpatiallyLoadedPropertyName(), AActor::StaticClass()));
for (TSharedRef<IPropertyHandle> Property : PropertiesToHide)
{
DetailBuilder.HideProperty(Property);
}
if (CustomizedLevel)
{
const bool bIsPartitionedWorld = UWorld::IsPartitionedWorld(CustomizedLevel->GetWorld());
IDetailCategoryBuilder& WorldCategory = DetailBuilder.EditCategory("World");
if (GetDefault<UEditorExperimentalSettings>()->bEnableOneFilePerActorSupport)
{
WorldCategory.AddCustomRow(LOCTEXT("LevelUseExternalActorsRow", "LevelUseExternalActors"), true)
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("LevelUseExternalActors", "Use External Actors"))
.ToolTipText(LOCTEXT("ActorPackagingMode_ToolTip", "Use external actors, new actor spawned in this level will be external and existing external actors will be loaded on load."))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(!bIsPartitionedWorld)
]
.ValueContent()
[
SNew(SCheckBox)
.OnCheckStateChanged(this, &FWorldSettingsDetails::OnUseExternalActorsChanged, CustomizedLevel)
.IsChecked(this, &FWorldSettingsDetails::IsUseExternalActorsChecked, CustomizedLevel)
.IsEnabled(!bIsPartitionedWorld)
];
}
const bool bIsUsingActorFolders = CustomizedLevel->IsUsingActorFolders();
if (bIsUsingActorFolders || GetDefault<UEditorExperimentalSettings>()->bEnableActorFolderObjectSupport)
{
WorldCategory.AddCustomRow(LOCTEXT("LevelUseActorFoldersRow", "LevelUseActorFolders"), true)
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("LevelUseActorFolders", "Use Actor Folder Objects"))
.ToolTipText(LOCTEXT("LevelUseActorFolders_ToolTip", "Use actor folder objects, actor folders of this level will be persistent in their own object."))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(!bIsUsingActorFolders)
]
.ValueContent()
[
SNew(SCheckBox)
.OnCheckStateChanged(this, &FWorldSettingsDetails::OnUseActorFoldersChanged, CustomizedLevel)
.IsChecked(this, &FWorldSettingsDetails::IsUsingActorFoldersChecked, CustomizedLevel)
.IsEnabled(!bIsUsingActorFolders)
];
}
if (bIsPartitionedWorld)
{
IDetailCategoryBuilder& WorldPartitionCategory = DetailBuilder.EditCategory("WorldPartition");
WorldPartitionCategory.AddCustomRow(LOCTEXT("DefaultWorldPartitionSettingsRow", "DefaultWorldPartitionSettings"), true)
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("DefaultWorldPartitionSettings", "Default World Partition Settings"))
.ToolTipText(LOCTEXT("DefaultWorldPartitionSettings_ToolTip", "Save or Reset the current World Partition default editor state"))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(bIsPartitionedWorld)
]
.ValueContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SNew(SButton)
.OnClicked_Lambda([CustomizedLevel]()
{
FScopedTransaction Transaction(LOCTEXT("ResetDefaultWorldPartitionSettings", "Reset Default World Partition Settings"));
CustomizedLevel->GetWorldSettings()->ResetDefaultWorldPartitionSettings();
return FReply::Handled();
})
[
SNew(STextBlock)
.Text(LOCTEXT("ResetButtonText", "Reset"))
.ToolTipText(LOCTEXT("ResetButtonToolTip", "Reset World Partition default editor state"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
+ SHorizontalBox::Slot()
[
SNew(SButton)
.OnClicked_Lambda([CustomizedLevel]()
{
FScopedTransaction Transaction(LOCTEXT("SaveDefaultWorldPartitionSettings", "Save Default World Partition Settings"));
CustomizedLevel->GetWorldSettings()->SaveDefaultWorldPartitionSettings();
return FReply::Handled();
})
[
SNew(STextBlock)
.Text(LOCTEXT("SaveButtonText", "Save"))
.ToolTipText(LOCTEXT("SaveButtonToolTip", "Save current World Partition editor state as map default"))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(bIsPartitionedWorld)
]
]
];
}
}
}
void FWorldSettingsDetails::OnUseActorFoldersChanged(ECheckBoxState BoxState, ULevel* Level)
{
if (Level && (BoxState == ECheckBoxState::Checked))
{
Level->SetUseActorFolders(true, /*bInteractiveMode*/ true);
}
}
ECheckBoxState FWorldSettingsDetails::IsUsingActorFoldersChecked(ULevel* Level) const
{
return Level->IsUsingActorFolders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void FWorldSettingsDetails::OnUseExternalActorsChanged(ECheckBoxState BoxState, ULevel* Level)
{
if (Level != nullptr)
{
// Validate we have a saved map
UPackage* LevelPackage = Level->GetOutermost();
if (LevelPackage == GetTransientPackage()
|| LevelPackage->HasAnyFlags(RF_Transient)
|| !FPackageName::IsValidLongPackageName(LevelPackage->GetName()))
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("UseExternalActorsSaveMap", "You need to save the level before enabling the `Use External Actors` option."));
return;
}
FScopedTransaction Transaction(LOCTEXT("WorldUseExternalActors", "Change World Use External Actors"));
Level->Modify();
Level->SetUseExternalActors(BoxState == ECheckBoxState::Checked);
FText MessageTitle(LOCTEXT("ConvertActorPackagingDialog", "Convert Actors Packaging"));
FText PackagingMode = Level->IsUsingExternalActors() ? LOCTEXT("ExternalActors", "External") : LOCTEXT("InternalActors", "Internal");
FText Message = FText::Format(LOCTEXT("ConvertActorPackagingMsg", "Do you want to convert all actors to {0} packaging as well?"), PackagingMode);
EAppReturnType::Type ConvertAnswer = FMessageDialog::Open(EAppMsgType::YesNo, Message, &MessageTitle);
// if the user accepts, convert all actors to what the new packaging mode will be
if (ConvertAnswer == EAppReturnType::Yes)
{
Level->ConvertAllActorsToPackaging(Level->IsUsingExternalActors());
}
}
}
ECheckBoxState FWorldSettingsDetails::IsUseExternalActorsChecked(ULevel* Level) const
{
return Level->IsUsingExternalActors() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
FLightmapCustomNodeBuilder::FLightmapCustomNodeBuilder(const TSharedPtr<FAssetThumbnailPool>& InThumbnailPool)
{
ThumbnailPool = InThumbnailPool;
}
FLightmapCustomNodeBuilder::~FLightmapCustomNodeBuilder()
{
FEditorDelegates::OnLightingBuildKept.RemoveAll(this);
FEditorDelegates::MapChange.RemoveAll(this);
FEditorDelegates::NewCurrentLevel.RemoveAll(this);
}
void FLightmapCustomNodeBuilder::SetOnRebuildChildren(FSimpleDelegate InOnRegenerateChildren)
{
OnRegenerateChildren = InOnRegenerateChildren;
FEditorDelegates::OnLightingBuildKept.AddSP(this, &FLightmapCustomNodeBuilder::HandleLightingBuildKept);
FEditorDelegates::MapChange.AddSP(this, &FLightmapCustomNodeBuilder::HandleMapChanged);
FEditorDelegates::NewCurrentLevel.AddSP(this, &FLightmapCustomNodeBuilder::HandleNewCurrentLevel);
}
void FLightmapCustomNodeBuilder::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow)
{
NodeRow.NameContent()
[
SNew( STextBlock )
.Text( LOCTEXT("LightmapHeaderRowContent", "Lightmaps") )
.Font( IDetailLayoutBuilder::GetDetailFont() )
];
NodeRow.ValueContent()
[
SNew( STextBlock )
.Text( this, &FLightmapCustomNodeBuilder::GetLightmapCountText )
.Font( IDetailLayoutBuilder::GetDetailFont() )
];
}
void FLightmapCustomNodeBuilder::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder)
{
RefreshLightmapItems();
for(TSharedPtr<FLightmapItem>& Item : LightmapItems)
{
ChildrenBuilder.AddCustomRow(LOCTEXT("LightMapsFilter", "Lightmaps"))
.WholeRowContent()
.HAlign(HAlign_Fill)
[
MakeLightMapList(Item)
];
}
}
FText FLightmapCustomNodeBuilder::GetLightmapCountText() const
{
return FText::Format( LOCTEXT("LightmapHeaderRowCount", "{0} Lightmap(s)"), FText::AsNumber(LightmapItems.Num()) );
}
void FLightmapCustomNodeBuilder::HandleLightingBuildKept()
{
OnRegenerateChildren.ExecuteIfBound();
}
void FLightmapCustomNodeBuilder::HandleMapChanged(uint32 MapChangeFlags)
{
OnRegenerateChildren.ExecuteIfBound();
}
void FLightmapCustomNodeBuilder::HandleNewCurrentLevel()
{
OnRegenerateChildren.ExecuteIfBound();
}
TSharedRef<SWidget> FLightmapCustomNodeBuilder::MakeLightMapList(TSharedPtr<FLightmapItem> LightMapItem)
{
if ( !ensure(LightMapItem.IsValid()) )
{
return SNullWidget::NullWidget;
}
const uint32 ThumbnailResolution = 64;
const uint32 ThumbnailBoxPadding = 4;
UObject* LightMapObject = FindObject<UObject>(NULL, *LightMapItem->ObjectPath);
FAssetData LightMapAssetData(LightMapObject);
FAssetThumbnailConfig ThumbnailConfig;
ThumbnailConfig.bAllowFadeIn = true;
TWeakPtr<FLightmapItem> LightmapWeakPtr = LightMapItem;
return
SNew(SBorder)
.BorderImage(nullptr)
.Padding(0.0f)
.OnMouseButtonUp(this, &FLightmapCustomNodeBuilder::OnMouseButtonUp, LightmapWeakPtr)
.OnMouseDoubleClick(this, &FLightmapCustomNodeBuilder::OnLightMapListMouseButtonDoubleClick, LightmapWeakPtr)
[
SNew(SHorizontalBox)
// Viewport
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew( SBox )
.WidthOverride( ThumbnailResolution + ThumbnailBoxPadding * 2 )
.HeightOverride( ThumbnailResolution + ThumbnailBoxPadding * 2 )
[
// Drop shadow border
SNew(SBorder)
.Padding(ThumbnailBoxPadding)
.BorderImage( FAppStyle::GetBrush("ContentBrowser.ThumbnailShadow") )
[
LightMapItem->Thumbnail->MakeThumbnailWidget(ThumbnailConfig)
]
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(6, 0, 0, 0)
.VAlign(VAlign_Center)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 1)
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(FText::FromName(LightMapAssetData.AssetName) )
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 1)
[
// Class
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(FText::FromString(LightMapAssetData.AssetClassPath.ToString()))
]
]
];
}
TSharedPtr<SWidget> FLightmapCustomNodeBuilder::OnGetLightMapContextMenuContent(TSharedPtr<FLightmapItem> Lightmap)
{
if (Lightmap.IsValid() )
{
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, NULL);
MenuBuilder.BeginSection("LightMapsContextMenuSection", LOCTEXT("LightMapsContextMenuHeading", "Options") );
{
MenuBuilder.AddMenuEntry(
LOCTEXT("ViewLightmapLabel", "View Lightmap"),
LOCTEXT("ViewLightmapTooltip", "Opens the texture editor with this lightmap."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FLightmapCustomNodeBuilder::ExecuteViewLightmap, Lightmap->ObjectPath))
);
}
MenuBuilder.EndSection(); //LightMapsContextMenuSection
return MenuBuilder.MakeWidget();
}
return nullptr;
}
FReply FLightmapCustomNodeBuilder::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, TWeakPtr<FLightmapItem> Lightmap)
{
if(MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
TSharedPtr<SWidget> MenuContent = OnGetLightMapContextMenuContent(Lightmap.Pin());
if(MenuContent.IsValid() && MouseEvent.GetEventPath() != nullptr)
{
const FVector2D& SummonLocation = MouseEvent.GetScreenSpacePosition();
FWidgetPath WidgetPath = *MouseEvent.GetEventPath();
FSlateApplication::Get().PushMenu(WidgetPath.Widgets.Last().Widget, WidgetPath, MenuContent.ToSharedRef(), SummonLocation, FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
}
return FReply::Handled();
}
return FReply::Unhandled();
}
FReply FLightmapCustomNodeBuilder::OnLightMapListMouseButtonDoubleClick(const FGeometry& MyGeom, const FPointerEvent& PointerEvent, TWeakPtr<FLightmapItem> SelectedLightmap)
{
if ( ensure(SelectedLightmap.IsValid()) )
{
ExecuteViewLightmap(SelectedLightmap.Pin()->ObjectPath);
}
return FReply::Handled();
}
void FLightmapCustomNodeBuilder::ExecuteViewLightmap(FString SelectedLightmapPath)
{
UObject* LightMapObject = FindObject<UObject>(NULL, *SelectedLightmapPath);
if ( LightMapObject )
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(LightMapObject);
}
}
void FLightmapCustomNodeBuilder::RefreshLightmapItems()
{
LightmapItems.Empty();
FWorldContext& Context = GEditor->GetEditorWorldContext();
UWorld* World = Context.World();
if ( World )
{
TArray<UTexture2D*> LightMapsAndShadowMaps;
World->GetLightMapsAndShadowMaps(World->GetCurrentLevel(), LightMapsAndShadowMaps, false);
for ( auto ObjIt = LightMapsAndShadowMaps.CreateConstIterator(); ObjIt; ++ObjIt )
{
UTexture2D* CurrentObject = *ObjIt;
if (CurrentObject)
{
FAssetData AssetData = FAssetData(CurrentObject);
const uint32 ThumbnailResolution = 64;
TSharedPtr<FAssetThumbnail> LightMapThumbnail = MakeShareable( new FAssetThumbnail( AssetData, ThumbnailResolution, ThumbnailResolution, ThumbnailPool ) );
TSharedPtr<FLightmapItem> NewItem = MakeShareable( new FLightmapItem(CurrentObject->GetPathName(), LightMapThumbnail) );
LightmapItems.Add(NewItem);
}
}
}
}
#undef LOCTEXT_NAMESPACE