Files
UnrealEngine/Engine/Source/Editor/DetailCustomizations/Private/DataTableCustomization.cpp
2025-05-18 13:04:45 +08:00

238 lines
7.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DataTableCustomization.h"
#include "AssetRegistry/AssetData.h"
#include "Containers/Map.h"
#include "DataTableEditorUtils.h"
#include "Delegates/Delegate.h"
#include "DetailWidgetRow.h"
#include "Editor.h"
#include "Engine/DataTable.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Commands/UIAction.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "IDetailChildrenBuilder.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Misc/Attribute.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditorModule.h"
#include "PropertyHandle.h"
#include "Templates/Casts.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Text/STextBlock.h"
class SToolTip;
#define LOCTEXT_NAMESPACE "FDataTableCustomizationLayout"
void FDataTableCustomizationLayout::CustomizeHeader(TSharedRef<class IPropertyHandle> InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
this->StructPropertyHandle = InStructPropertyHandle;
if (StructPropertyHandle->HasMetaData(TEXT("RowType")))
{
const FString& RowType = StructPropertyHandle->GetMetaData(TEXT("RowType"));
RowTypeFilter = FName(*RowType);
RowFilterStruct = UClass::TryFindTypeSlow<UScriptStruct>(RowType);
}
FSimpleDelegate OnDataTableChangedDelegate = FSimpleDelegate::CreateSP(this, &FDataTableCustomizationLayout::OnDataTableChanged);
StructPropertyHandle->SetOnPropertyValueChanged(OnDataTableChangedDelegate);
HeaderRow
.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
];
FDataTableEditorUtils::AddSearchForReferencesContextMenu(HeaderRow, FExecuteAction::CreateSP(this, &FDataTableCustomizationLayout::OnSearchForReferences));
}
void FDataTableCustomizationLayout::CustomizeChildren(TSharedRef<class IPropertyHandle> InStructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
/** Get all the existing property handles */
DataTablePropertyHandle = InStructPropertyHandle->GetChildHandle("DataTable");
RowNamePropertyHandle = InStructPropertyHandle->GetChildHandle("RowName");
const FName PropertyName = InStructPropertyHandle->GetProperty()->GetFName();
if (DataTablePropertyHandle->IsValidHandle() && RowNamePropertyHandle->IsValidHandle())
{
/** Setup Change callback */
FSimpleDelegate OnDataTableChangedDelegate = FSimpleDelegate::CreateSP(this, &FDataTableCustomizationLayout::OnDataTableChanged);
DataTablePropertyHandle->SetOnPropertyValueChanged(OnDataTableChangedDelegate);
/** Construct a asset picker widget with a custom filter */
StructBuilder.AddCustomRow(LOCTEXT("DataTable_TableName", "Data Table"))
.RowTag(PropertyName)
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("DataTable_TableName", "Data Table"))
.Font(StructCustomizationUtils.GetRegularFont())
]
.ValueContent()
.MaxDesiredWidth(0.0f) // don't constrain the combo button width
[
SNew(SObjectPropertyEntryBox)
.PropertyHandle(DataTablePropertyHandle)
.AllowedClass(UDataTable::StaticClass())
.OnShouldFilterAsset(this, &FDataTableCustomizationLayout::ShouldFilterAsset)
];
FPropertyComboBoxArgs ComboArgs(RowNamePropertyHandle,
FOnGetPropertyComboBoxStrings::CreateSP(this, &FDataTableCustomizationLayout::OnGetRowStrings),
FOnGetPropertyComboBoxValue::CreateSP(this, &FDataTableCustomizationLayout::OnGetRowValueString));
ComboArgs.ShowSearchForItemCount = 1;
/** Construct a combo box widget to select from a list of valid options */
StructBuilder.AddCustomRow(LOCTEXT("DataTable_RowName", "Row Name"))
.RowTag(PropertyName)
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("DataTable_RowName", "Row Name"))
.Font(StructCustomizationUtils.GetRegularFont())
]
.ValueContent()
.MaxDesiredWidth(0.0f) // don't constrain the combo button width
[
PropertyCustomizationHelpers::MakePropertyComboBox(ComboArgs)
];
}
}
bool FDataTableCustomizationLayout::GetCurrentValue(UDataTable*& OutDataTable, FName& OutName) const
{
if (RowNamePropertyHandle.IsValid() && RowNamePropertyHandle->IsValidHandle() && DataTablePropertyHandle.IsValid() && DataTablePropertyHandle->IsValidHandle())
{
// If either handle is multiple value or failure, fail
UObject* SourceDataTable = nullptr;
if (DataTablePropertyHandle->GetValue(SourceDataTable) == FPropertyAccess::Success)
{
OutDataTable = Cast<UDataTable>(SourceDataTable);
if (RowNamePropertyHandle->GetValue(OutName) == FPropertyAccess::Success)
{
return true;
}
}
}
return false;
}
void FDataTableCustomizationLayout::OnSearchForReferences()
{
UDataTable* DataTable;
FName RowName;
if (GetCurrentValue(DataTable, RowName) && DataTable)
{
TArray<FAssetIdentifier> AssetIdentifiers;
AssetIdentifiers.Add(FAssetIdentifier(DataTable, RowName));
FEditorDelegates::OnOpenReferenceViewer.Broadcast(AssetIdentifiers, FReferenceViewerParams());
}
}
FString FDataTableCustomizationLayout::OnGetRowValueString() const
{
if (!RowNamePropertyHandle.IsValid() || !RowNamePropertyHandle->IsValidHandle())
{
return FString();
}
FName RowNameValue;
const FPropertyAccess::Result RowResult = RowNamePropertyHandle->GetValue(RowNameValue);
if (RowResult == FPropertyAccess::Success)
{
if (RowNameValue.IsNone())
{
return LOCTEXT("DataTable_None", "None").ToString();
}
return RowNameValue.ToString();
}
else if (RowResult == FPropertyAccess::Fail)
{
return LOCTEXT("DataTable_None", "None").ToString();
}
else
{
return LOCTEXT("MultipleValues", "Multiple Values").ToString();
}
}
void FDataTableCustomizationLayout::OnGetRowStrings(TArray< TSharedPtr<FString> >& OutStrings, TArray<TSharedPtr<SToolTip>>& OutToolTips, TArray<bool>& OutRestrictedItems) const
{
UDataTable* DataTable = nullptr;
FName IgnoredRowName;
// Ignore return value as we will show rows if table is the same but row names are multiple values
GetCurrentValue(DataTable, IgnoredRowName);
TArray<FName> AllRowNames;
if (DataTable != nullptr)
{
for (TMap<FName, uint8*>::TConstIterator Iterator(DataTable->GetRowMap()); Iterator; ++Iterator)
{
AllRowNames.Add(Iterator.Key());
}
// Sort the names alphabetically.
AllRowNames.Sort(FNameLexicalLess());
}
for (const FName& RowName : AllRowNames)
{
OutStrings.Add(MakeShared<FString>(RowName.ToString()));
OutRestrictedItems.Add(false);
}
}
void FDataTableCustomizationLayout::OnDataTableChanged()
{
UDataTable* CurrentTable;
FName OldName;
// Clear name on table change if no longer valid
if (GetCurrentValue(CurrentTable, OldName))
{
if (!CurrentTable || !CurrentTable->FindRowUnchecked(OldName))
{
RowNamePropertyHandle->SetValue(FName());
}
}
}
bool FDataTableCustomizationLayout::ShouldFilterAsset(const struct FAssetData& AssetData)
{
if (!RowTypeFilter.IsNone())
{
static const FName RowStructureTagName("RowStructure");
FString RowStructure;
if (AssetData.GetTagValue<FString>(RowStructureTagName, RowStructure))
{
if (RowStructure == RowTypeFilter.ToString())
{
return false;
}
// This is slow, but at the moment we don't have an alternative to the short struct name search
UScriptStruct* RowStruct = UClass::TryFindTypeSlow<UScriptStruct>(RowStructure);
if (RowStruct && RowFilterStruct && RowStruct->IsChildOf(RowFilterStruct))
{
return false;
}
}
return true;
}
return false;
}
#undef LOCTEXT_NAMESPACE