Files
UnrealEngine/Engine/Plugins/Experimental/InstanceDataObjectFixupTool/Source/Private/InstanceDataObjectFixupDetailCustomization.cpp
2025-05-18 13:04:45 +08:00

368 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InstanceDataObjectFixupDetailCustomization.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "IDetailChildrenBuilder.h"
#include "InstanceDataObjectFixupPanel.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
#include "Widgets/Input/SComboButton.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "UObject/PropertyBagRepository.h"
#define LOCTEXT_NAMESPACE "InstanceDataObjectFixupDetails"
/////////////////////////////////////////////////////////////////
// FInstanceDataObjectNameWidgetOverride
/////////////////////////////////////////////////////////////////
FInstanceDataObjectNameWidgetOverride::FInstanceDataObjectNameWidgetOverride(const TSharedRef<FInstanceDataObjectFixupPanel>& InDiffPanel)
: DiffPanel(InDiffPanel)
{
}
TSharedRef<SWidget> FInstanceDataObjectNameWidgetOverride::CustomizeName(TSharedRef<SWidget> InnerNameContent, FPropertyPath& Path)
{
const TSharedRef<SWidget> NameContent = SNew(SWidgetSwitcher)
.WidgetIndex(this, &FInstanceDataObjectNameWidgetOverride::GetNameWidgetIndex, Path)
+SWidgetSwitcher::Slot()
[
InnerNameContent
]
+SWidgetSwitcher::Slot()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(EVerticalAlignment::VAlign_Center)
.AutoWidth()
[
SNew(STextBlock)
.Visibility(this, &FInstanceDataObjectNameWidgetOverride::DeletionSymbolVisibility, Path)
.Text(LOCTEXT("MarkedForDeletion", ""))
]
+SHorizontalBox::Slot()
.VAlign(EVerticalAlignment::VAlign_Center)
.AutoWidth()
[
SNew(SComboButton)
.Visibility(EVisibility::Visible)
.OnGetMenuContent_Raw(this, &FInstanceDataObjectNameWidgetOverride::GeneratePropertyRedirectMenu, Path)
.ButtonContent()
[
InnerNameContent
]
]
];
return NameContent;
}
TSet<FPropertyPath> FInstanceDataObjectNameWidgetOverride::GetRedirectOptions(const UStruct* Struct, void* Value) const
{
TSet<FPropertyPath> Result;
GetRedirectOptions(Struct, Value, {}, Result);
return Result;
}
void FInstanceDataObjectNameWidgetOverride::GetRedirectOptions(const UStruct* Struct, void* Value, const FPropertyPath& Path, TSet<FPropertyPath>& OutPaths) const
{
for (FProperty* SubProperty : TFieldRange<FProperty>(Struct))
{
if (SubProperty->HasAnyPropertyFlags(CPF_Transient))
{
continue;
}
if (!SubProperty->HasAnyPropertyFlags(CPF_Edit | CPF_EditConst))
{
continue;
}
if (SubProperty->ArrayDim == 1)
{
const FPropertyPath SubPath = Path.ExtendPath(FPropertyInfo(SubProperty)).Get();
GetRedirectOptions(SubProperty, SubProperty->ContainerPtrToValuePtr<void>(Value), SubPath, OutPaths);
}
else
{
for (int32 ArrayIndex = 0; ArrayIndex < SubProperty->ArrayDim; ++ArrayIndex)
{
const FPropertyPath SubPath = Path.ExtendPath(FPropertyInfo(SubProperty, ArrayIndex)).Get();
GetRedirectOptions(SubProperty, SubProperty->ContainerPtrToValuePtr<void>(Value, ArrayIndex), SubPath, OutPaths);
}
}
}
}
void FInstanceDataObjectNameWidgetOverride::GetRedirectOptions(const FProperty* Property, void* Value, const FPropertyPath& Path, TSet<FPropertyPath>& OutPaths) const
{
if (Property->GetBoolMetaData(TEXT("isLoose")))
{
// don't include loose properties as options
return;
}
if (!DiffPanel.Pin()->RedirectedPropertyTree->Find(Path))
{
OutPaths.Add(Path);
}
if (const FStructProperty* AsStructProperty = CastField<FStructProperty>(Property))
{
GetRedirectOptions(AsStructProperty->Struct, Value, Path, OutPaths);
}
if (const FObjectProperty* AsObjectProperty = CastField<FObjectProperty>(Property))
{
if (AsObjectProperty->HasAnyPropertyFlags(CPF_InstancedReference))
{
if (TObjectPtr<UObject> Object = AsObjectProperty->GetObjectPropertyValue(Value))
{
UE::FPropertyBagRepository& PropertyBagRepository = UE::FPropertyBagRepository::Get();
if (UObject* Found = PropertyBagRepository.FindInstanceDataObject(Object))
{
Object = Found;
}
GetRedirectOptions(Object->GetClass(), Object, Path, OutPaths);
}
}
}
else if (const FArrayProperty* AsArrayProperty = CastField<FArrayProperty>(Property))
{
FScriptArrayHelper Array(AsArrayProperty, Value);
for (int32 ArrayIndex = 0; ArrayIndex < Array.Num(); ++ArrayIndex)
{
const FPropertyPath SubPath = Path.ExtendPath(FPropertyInfo(AsArrayProperty->Inner, ArrayIndex)).Get();
GetRedirectOptions(AsArrayProperty->Inner, Array.GetElementPtr(ArrayIndex), SubPath, OutPaths);
}
}
else if (const FSetProperty* AsSetProperty = CastField<FSetProperty>(Property))
{
FScriptSetHelper Set(AsSetProperty, Value);
for (FScriptSetHelper::FIterator Iterator = Set.CreateIterator(); Iterator; ++Iterator)
{
const FPropertyPath SubPath = Path.ExtendPath(FPropertyInfo(AsSetProperty->ElementProp, Iterator.GetLogicalIndex())).Get();
GetRedirectOptions(AsSetProperty->ElementProp, Set.GetElementPtr(Iterator), SubPath, OutPaths);
}
}
else if (const FMapProperty* AsMapProperty = CastField<FMapProperty>(Property))
{
FScriptMapHelper Map(AsMapProperty, Value);
for (FScriptMapHelper::FIterator Iterator = Map.CreateIterator(); Iterator; ++Iterator)
{
const FPropertyPath SubPath = Path.ExtendPath(FPropertyInfo(AsMapProperty->ValueProp, Iterator.GetLogicalIndex())).Get();
GetRedirectOptions(AsMapProperty->ValueProp, Map.GetValuePtr(Iterator), SubPath, OutPaths);
}
}
}
int32 FInstanceDataObjectNameWidgetOverride::GetNameWidgetIndex(FPropertyPath Path) const
{
if (const TSharedPtr<FInstanceDataObjectFixupPanel> Panel = DiffPanel.Pin())
{
if (Panel->HasViewFlag(FInstanceDataObjectFixupPanel::EViewFlags::AllowRemapLooseProperties))
{
if (Panel->RevertInfo.Contains(Path))
{
return DisplayRedirectMenu;
}
if (const FProperty* Property = Path.GetLeafMostProperty().Property.Get())
{
if (Property->GetBoolMetaData(TEXT("isLoose")))
{
return DisplayRedirectMenu;
}
}
}
}
return DisplayRegularName;
}
static FText GetCleanVersePath(const FPropertyPath& Path)
{
FString PathString = Path.ToString();
int32 I = PathString.Find(TEXT("__verse_0x"));
while (I < GetNum(PathString) && I != INDEX_NONE)
{
PathString.RemoveAt(I, 19, EAllowShrinking::No);
I = PathString.Find(TEXT("__verse_0x"), ESearchCase::IgnoreCase, ESearchDir::FromStart, I);
}
return FText::FromString(PathString);
}
TSharedRef<SWidget> FInstanceDataObjectNameWidgetOverride::GeneratePropertyRedirectMenu(FPropertyPath Path) const
{
FMenuBuilder MenuBuilder(true, nullptr);
const TSharedPtr<FInstanceDataObjectFixupPanel> Panel = DiffPanel.Pin();
if (!Panel)
{
return MenuBuilder.MakeWidget();
}
const FPropertyPath& OriginalPath = Panel->GetOriginalPath(Path);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("ResetRedirect", "Reset"));
if (OriginalPath != Path || Panel->MarkedForDelete.Contains(OriginalPath))
{
FText OriginalPathText = GetCleanVersePath(OriginalPath);
FText Tooltip = FText::Format(LOCTEXT("ResetTooltip", "Reset back to {0}"), OriginalPathText);
MenuBuilder.AddMenuEntry(OriginalPathText, Tooltip, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Panel.Get(), &FInstanceDataObjectFixupPanel::OnRedirectProperty, Path, OriginalPath))
, NAME_None
, EUserInterfaceActionType::RadioButton);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection(NAME_None, LOCTEXT("Delete", "Delete"));
{
if (!Panel->MarkedForDelete.Contains(OriginalPath)) // check that it wasn't already marked as deleted
{
FText DisplayName = LOCTEXT("MarkForDeletion", "Mark For Deletion");
FText Tooltip = LOCTEXT("MarkForDeletionTooltip", "Mark this property for deletion");
MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Panel.Get(), &FInstanceDataObjectFixupPanel::OnMarkForDelete, Path))
, NAME_None
, EUserInterfaceActionType::RadioButton);
}
}
MenuBuilder.EndSection();
UObject* FirstInstanceDataObject = Panel->Instances[0];
TSet<FPropertyPath> RedirectOptions = GetRedirectOptions(FirstInstanceDataObject->GetClass(), FirstInstanceDataObject);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("MoveProperty", "Move"));
{
for (const FPropertyPath& Option : RedirectOptions)
{
const FProperty* ThisProperty = Path.GetLeafMostProperty().Property.Get();
const FProperty* OptionProperty = Option.GetLeafMostProperty().Property.Get();
if (ThisProperty->GetFName() == OptionProperty->GetFName())
{
if (OptionProperty->SameType(ThisProperty))
{
FText DisplayName = GetCleanVersePath(Option);
FText Tooltip = FText::Format(LOCTEXT("MovePropertyTooltip", "Move property to '{0}'"), DisplayName);
MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Panel.Get(), &FInstanceDataObjectFixupPanel::OnRedirectProperty, Path, Option))
, NAME_None
, EUserInterfaceActionType::RadioButton);
}
}
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection(NAME_None, LOCTEXT("RenameProperty", "Rename"));
{
for (const FPropertyPath& Option : RedirectOptions)
{
const FProperty* ThisProperty = Path.GetLeafMostProperty().Property.Get();
const FProperty* OptionProperty = Option.GetLeafMostProperty().Property.Get();
if (ThisProperty->GetFName() == OptionProperty->GetFName())
{
continue; // handled in the "move" category
}
if (OptionProperty->SameType(ThisProperty))
{
FText DisplayName = GetCleanVersePath(Option);
FText Tooltip = FText::Format(LOCTEXT("RenamePropertyTooltip", "Rename property to '{0}'"), DisplayName);
MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Panel.Get(), &FInstanceDataObjectFixupPanel::OnRedirectProperty, Path, Option))
, NAME_None
, EUserInterfaceActionType::RadioButton);
}
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection(NAME_None, LOCTEXT("ChangeToDifferingTypeConversion", "Convert Type"));
{
for (const FPropertyPath& Option : RedirectOptions)
{
const FProperty* ThisProperty = Path.GetLeafMostProperty().Property.Get();
const FProperty* OptionProperty = Option.GetLeafMostProperty().Property.Get();
if (OptionProperty->SameType(ThisProperty))
{
continue; // same type handled above
}
if (ThisProperty->GetFName() != OptionProperty->GetFName())
{
continue; // renames to handled below
}
if (FInstanceDataObjectFixupPanel::FTypeConverter Converter = Panel->CreateTypeConverter(Path, Option))
{
FText DisplayName = GetCleanVersePath(Option);
FText TypeName = FText::FromName(Option.GetLeafMostProperty().Property->GetID());
FText Warning = Converter.GetWarning();
FText Tooltip = FText::Format(LOCTEXT("ConvertTypeTooltip", "Change type to {0}"), TypeName);
if (!Warning.IsEmpty())
{
DisplayName = FText::Format(LOCTEXT("ConvertTypeDisplayNameWithWarning", "⚠{0}"), DisplayName);
Tooltip = FText::Format(LOCTEXT("ConvertTypeTooltipWithWarning", "⚠{0}\n Warning: {1}"), Tooltip, Warning);
}
MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Panel.Get(), &FInstanceDataObjectFixupPanel::OnRedirectProperty, Path, Option, Converter))
, NAME_None
, EUserInterfaceActionType::RadioButton);
}
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection(NAME_None, LOCTEXT("RenameToDifferingTypeConversion", "Convert Type and Rename"));
{
for (const FPropertyPath& Option : RedirectOptions)
{
const FProperty* ThisProperty = Path.GetLeafMostProperty().Property.Get();
const FProperty* OptionProperty = Option.GetLeafMostProperty().Property.Get();
if (OptionProperty->SameType(ThisProperty))
{
continue; // same type handled above
}
if (ThisProperty->GetFName() == OptionProperty->GetFName())
{
continue; // renames to handled below
}
if (FInstanceDataObjectFixupPanel::FTypeConverter Converter = Panel->CreateTypeConverter(Path, Option))
{
FText DisplayName = GetCleanVersePath(Option);
FText PropDisplayName = Option.GetLeafMostProperty().Property->GetDisplayNameText();
FText TypeName = FText::FromName(Option.GetLeafMostProperty().Property->GetID());
FText Warning = Converter.GetWarning();
FText Tooltip = FText::Format(LOCTEXT("ConvertTypeAndRenameTooltip", "Change type to {0} and rename to {1}"), TypeName, PropDisplayName);
if (!Warning.IsEmpty())
{
DisplayName = FText::Format(LOCTEXT("ConvertTypeAndRenameDisplayNameWithWarning", "⚠{0}"), DisplayName);
Tooltip = FText::Format(LOCTEXT("ConvertTypeAndRenameTooltipWithWarning", "⚠{0}\n Warning: {1}"), Tooltip, Warning);
}
MenuBuilder.AddMenuEntry(DisplayName, Tooltip, FSlateIcon()
, FUIAction(FExecuteAction::CreateSP(Panel.Get(), &FInstanceDataObjectFixupPanel::OnRedirectProperty, Path, Option, Converter))
, NAME_None
, EUserInterfaceActionType::RadioButton);
}
}
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
EVisibility FInstanceDataObjectNameWidgetOverride::DeletionSymbolVisibility(FPropertyPath Path) const
{
if (const TSharedPtr<FInstanceDataObjectFixupPanel> Panel = DiffPanel.Pin())
{
return Panel->MarkedForDelete.Contains(Path) ? EVisibility::Visible : EVisibility::Collapsed;
}
return EVisibility::Collapsed;
}
EVisibility FInstanceDataObjectNameWidgetOverride::ValueContentVisibility(FPropertyPath Path) const
{
if (const TSharedPtr<FInstanceDataObjectFixupPanel> Panel = DiffPanel.Pin())
{
return Panel->MarkedForDelete.Contains(Path) ? EVisibility::Collapsed : EVisibility::Visible;
}
return EVisibility::Visible;
}
#undef LOCTEXT_NAMESPACE