Files
2025-05-18 13:04:45 +08:00

370 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DataRegistryIdCustomization.h"
#include "DataRegistrySubsystem.h"
#include "DataTableEditorUtils.h"
#include "IDetailChildrenBuilder.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Images/SImage.h"
#include "PropertyCustomizationHelpers.h"
#include "GameplayTagsEditorModule.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(DataRegistryIdCustomization)
#define LOCTEXT_NAMESPACE "DataRegistryEditor"
FText FDataRegistryIdEditWrapper::GetPreviewDescription() const
{
UDataRegistrySubsystem* Subsystem = GEngine->GetEngineSubsystem<UDataRegistrySubsystem>();
if (Subsystem)
{
return Subsystem->GetDisplayTextForId(RegistryId);
}
return RegistryId.ToText();
}
void SDataRegistryItemNameWidget::Construct(const FArguments& InArgs)
{
OnGetDisplayText = InArgs._OnGetDisplayText;
OnGetId = InArgs._OnGetId;
OnSetId = InArgs._OnSetId;
OnGetCustomItemNames = InArgs._OnGetCustomItemNames;
bAllowClear = InArgs._bAllowClear;
Subsystem = GEngine->GetEngineSubsystem<UDataRegistrySubsystem>();
check(Subsystem);
FPropertyComboBoxArgs NameArgs(nullptr,
FOnGetPropertyComboBoxStrings::CreateSP(this, &SDataRegistryItemNameWidget::OnGetNameStrings),
FOnGetPropertyComboBoxValue::CreateSP(this, &SDataRegistryItemNameWidget::OnGetNameValueString),
FOnPropertyComboBoxValueSelected::CreateSP(this, &SDataRegistryItemNameWidget::OnNameSelected));
NameArgs.ShowSearchForItemCount = 1;
this->ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1)
[
SAssignNew(ValueSwitcher, SWidgetSwitcher)
.WidgetIndex(0)
+ SWidgetSwitcher::Slot()
[
PropertyCustomizationHelpers::MakePropertyComboBox(NameArgs)
]
+ SWidgetSwitcher::Slot()
[
SNew(SComboButton)
.OnGetMenuContent(this, &SDataRegistryItemNameWidget::GetTagContent)
.OnMenuOpenChanged(this, &SDataRegistryItemNameWidget::OnTagUIOpened)
.MenuPlacement(MenuPlacement_BelowAnchor)
.ButtonContent()
[
SNew(STextBlock)
.Text(this, &SDataRegistryItemNameWidget::OnGetNameValueText)
.Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
]
]
+ SHorizontalBox::Slot()
.Padding(FMargin(2, 0, 0, 0))
.AutoWidth()
.VAlign(EVerticalAlignment::VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.WarningWithColor"))
.DesiredSizeOverride(FVector2D(16, 16))
.ToolTipText(LOCTEXT("InvalidRegistryId", "Unknown Item Name, this will not work at runtime with the current registry settings"))
.Visibility(this, &SDataRegistryItemNameWidget::GetWarningVisibility)
]
];
}
void SDataRegistryItemNameWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
FDataRegistryId CurrentId = OnGetId.Execute();
// Refresh if type has changed, or it's a custom type that might be different based on external factors
if (CurrentId.RegistryType != CachedIdValue.RegistryType || CurrentId.RegistryType == FDataRegistryType::CustomContextType)
{
bool bClearInvalid = CachedIdValue.RegistryType.IsValid();
CachedIdValue = CurrentId;
OnTypeChanged(bClearInvalid);
}
else if (CurrentId.ItemName != CachedIdValue.ItemName)
{
CachedIdValue.ItemName = CurrentId.ItemName;
}
}
void SDataRegistryItemNameWidget::OnNameSelected(const FString& NameString)
{
FDataRegistryId NewId = CachedIdValue;
NewId.ItemName = FName(*NameString);
OnSetId.Execute(NewId);
}
void SDataRegistryItemNameWidget::OnTypeChanged(bool bClearInvalid)
{
int32 SwitcherIndex = 0;
CachedIds.Reset();
if (CachedIdValue.RegistryType == FDataRegistryType::CustomContextType)
{
// For fake custom type, use callback
TArray<FName> CustomNames;
if (OnGetCustomItemNames.IsBound())
{
OnGetCustomItemNames.Execute(CustomNames);
for (FName CustomName : CustomNames)
{
CachedIds.Add(FDataRegistryId(FDataRegistryType::CustomContextType, CustomName));
}
}
}
UDataRegistry* Registry = Subsystem->GetRegistryForType(CachedIdValue.RegistryType);
if (Registry)
{
Registry->GetPossibleRegistryIds(CachedIds);
FDataRegistryIdFormat IdFormat = Registry->GetIdFormat();
if (IdFormat.BaseGameplayTag.IsValid())
{
// Switch to the tag widget
SwitcherIndex = 1;
CachedBaseGameplayTag = IdFormat.BaseGameplayTag.ToString();
}
}
if (!CachedIdValue.ItemName.IsNone() && !CachedIds.Contains(CachedIdValue) && bClearInvalid)
{
// Name no longer valid, clear
OnSetId.Execute(FDataRegistryId(CachedIdValue.RegistryType, NAME_None));
}
ValueSwitcher->SetActiveWidgetIndex(SwitcherIndex);
}
TSharedRef<SWidget> SDataRegistryItemNameWidget::GetTagContent()
{
CachedTag = MakeShared<FGameplayTag>();
FDataRegistryId CurrentId = OnGetId.Execute();
if (CurrentId.IsValid())
{
FGameplayTag NameTag = FGameplayTag::RequestGameplayTag(CurrentId.ItemName, false);
if (NameTag.IsValid())
{
*CachedTag = NameTag;
}
}
return SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.MaxHeight(400)
[
IGameplayTagsEditorModule::Get().MakeGameplayTagWidget(FOnSetGameplayTag::CreateSP(this, &SDataRegistryItemNameWidget::OnTagChanged), CachedTag, CachedBaseGameplayTag)
];
}
void SDataRegistryItemNameWidget::OnTagUIOpened(bool bIsOpened)
{
if (bIsOpened)
{
// TODO Investigate tag focus issues in general
/* TSharedPtr<SGameplayTagWidget> TagWidget = LastTagWidget.Pin();
if (TagWidget.IsValid())
{
EditButton->SetMenuContentWidgetToFocus(TagWidget->GetWidgetToFocusOnOpen());
}*/
}
}
void SDataRegistryItemNameWidget::OnTagChanged(const FGameplayTag& NewTag)
{
OnNameSelected(NewTag.GetTagName().ToString());
}
void SDataRegistryItemNameWidget::OnGetNameStrings(TArray< TSharedPtr<FString> >& OutStrings, TArray<TSharedPtr<SToolTip>>& OutToolTips, TArray<bool>& OutRestrictedItems) const
{
// Always add none
OutStrings.Add(MakeShared<FString>(TEXT("None")));
OutRestrictedItems.Add(false);
for (const FDataRegistryId& CurrentID : CachedIds)
{
OutStrings.Add(MakeShared<FString>(CurrentID.ItemName.ToString()));
OutRestrictedItems.Add(false);
}
}
FString SDataRegistryItemNameWidget::OnGetNameValueString() const
{
return OnGetNameValueText().ToString();
}
FText SDataRegistryItemNameWidget::OnGetNameValueText() const
{
return OnGetDisplayText.Execute();
}
EVisibility SDataRegistryItemNameWidget::GetWarningVisibility() const
{
if (CachedIdValue.RegistryType.IsValid() && !CachedIdValue.ItemName.IsNone() && !CachedIds.Contains(CachedIdValue))
{
return EVisibility::Visible;
}
return EVisibility::Collapsed;
}
TSharedRef<IPropertyTypeCustomization> FDataRegistryIdCustomization::MakeInstance()
{
return MakeShareable(new FDataRegistryIdCustomization);
}
void FDataRegistryIdCustomization::CustomizeHeader(TSharedRef<class IPropertyHandle> InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
this->StructPropertyHandle = InStructPropertyHandle;
HeaderRow
.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
];
FDataTableEditorUtils::AddSearchForReferencesContextMenu(HeaderRow, FExecuteAction::CreateSP(this, &FDataRegistryIdCustomization::OnSearchForReferences));
}
void FDataRegistryIdCustomization::CustomizeChildren(TSharedRef<class IPropertyHandle> InStructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
/** Get all the existing property handles, need the name inside the type */
TypePropertyHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FDataRegistryId, RegistryType))->GetChildHandle(FName("Name"));
NamePropertyHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FDataRegistryId, ItemName));
if (TypePropertyHandle->IsValidHandle() && NamePropertyHandle->IsValidHandle())
{
FName FilterStructName;
if (StructPropertyHandle->HasMetaData(FDataRegistryType::ItemStructMetaData))
{
const FString& RowType = StructPropertyHandle->GetMetaData(FDataRegistryType::ItemStructMetaData);
FilterStructName = FName(*RowType);
}
FPropertyComboBoxArgs TypeArgs(TypePropertyHandle,
FOnGetPropertyComboBoxStrings::CreateStatic(FDataRegistryEditorModule::GenerateDataRegistryTypeComboBoxStrings, true, FilterStructName),
FOnGetPropertyComboBoxValue::CreateSP(this, &FDataRegistryIdCustomization::OnGetTypeValueString));
TypeArgs.ShowSearchForItemCount = 1;
/** Construct a combo box widget to select type */
StructBuilder.AddCustomRow(LOCTEXT("RegistryType", "Registry Type"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("RegistryType", "Registry Type"))
.Font(StructCustomizationUtils.GetRegularFont())
]
.ValueContent()
.MaxDesiredWidth(0.0f)
[
PropertyCustomizationHelpers::MakePropertyComboBox(TypeArgs)
];
/** Construct a combo box widget to select name */
StructBuilder.AddCustomRow(LOCTEXT("ItemName", "Item Name"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("ItemName", "Item Name"))
.Font(FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
]
.ValueContent()
.MaxDesiredWidth(0.0f)
[
SNew(SDataRegistryItemNameWidget)
.OnGetDisplayText(this, &FDataRegistryIdCustomization::OnGetNameValueText)
.OnGetId(this, &FDataRegistryIdCustomization::GetCurrentValue)
.OnSetId(this, &FDataRegistryIdCustomization::SetCurrentValue)
.bAllowClear(true)
];
}
}
FDataRegistryId FDataRegistryIdCustomization::GetCurrentValue() const
{
FDataRegistryId OutId;
if (TypePropertyHandle.IsValid() && TypePropertyHandle->IsValidHandle() && NamePropertyHandle.IsValid() && NamePropertyHandle->IsValidHandle())
{
TypePropertyHandle->GetValue(OutId.RegistryType);
NamePropertyHandle->GetValue(OutId.ItemName);
}
return OutId;
}
void FDataRegistryIdCustomization::SetCurrentValue(FDataRegistryId NewId)
{
FDataRegistryId CurrentId = GetCurrentValue();
// Always set value, only set type if different
NamePropertyHandle->SetValue(NewId.ItemName);
if (NewId.RegistryType != CurrentId.RegistryType)
{
TypePropertyHandle->SetValue(NewId.RegistryType.GetName());
}
}
void FDataRegistryIdCustomization::OnSearchForReferences()
{
// TODO implement or remove
}
FString FDataRegistryIdCustomization::OnGetTypeValueString() const
{
FDataRegistryId CurrentId = GetCurrentValue();
if (CurrentId.RegistryType.IsValid())
{
return CurrentId.RegistryType.ToString();
}
FName TempName;
if (TypePropertyHandle.IsValid() && TypePropertyHandle->GetValue(TempName) == FPropertyAccess::MultipleValues)
{
return LOCTEXT("MultipleValues", "Multiple Values").ToString();
}
return LOCTEXT("NoneValue", "None").ToString();
}
FText FDataRegistryIdCustomization::OnGetNameValueText() const
{
FDataRegistryId CurrentId = GetCurrentValue();
if (!CurrentId.ItemName.IsNone())
{
return FText::FromName(CurrentId.ItemName);
}
FName TempName;
if (NamePropertyHandle.IsValid() && NamePropertyHandle->GetValue(TempName) == FPropertyAccess::MultipleValues)
{
return LOCTEXT("MultipleValues", "Multiple Values");
}
return LOCTEXT("NoneValue", "None");
}
#undef LOCTEXT_NAMESPACE