diff --git a/Source/StevesUEHelpers/Public/StevesFixedDataTableRowHandle.h b/Source/StevesUEHelpers/Public/StevesFixedDataTableRowHandle.h new file mode 100644 index 0000000..ec446fb --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesFixedDataTableRowHandle.h @@ -0,0 +1,12 @@ +#pragma once +#include "Engine/DataTable.h" + +#include "StevesFixedDataTableRowHandle.generated.h" + +/// Just a type to denote that this table row handle should be edited differently +USTRUCT(BlueprintType) +struct STEVESUEHELPERS_API FStevesFixedDataTableRowHandle : public FDataTableRowHandle +{ + GENERATED_USTRUCT_BODY() +}; + \ No newline at end of file diff --git a/Source/StevesUEHelpersEd/Private/StevesFixedDataTableCustomisationLayout.cpp b/Source/StevesUEHelpersEd/Private/StevesFixedDataTableCustomisationLayout.cpp new file mode 100644 index 0000000..73cfad0 --- /dev/null +++ b/Source/StevesUEHelpersEd/Private/StevesFixedDataTableCustomisationLayout.cpp @@ -0,0 +1,194 @@ +#include "StevesFixedDataTableCustomisationLayout.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 "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 "FSsFixedDataTableCustomisationLayout" + +void FStevesFixedDataTableCustomisationLayout::CustomizeHeader(TSharedRef InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +{ + DataTablePropertyHandle = InStructPropertyHandle->GetChildHandle("DataTable"); + RowNamePropertyHandle = InStructPropertyHandle->GetChildHandle("RowName"); + + if (InStructPropertyHandle->HasMetaData(TEXT("DataTable"))) + { + // Find data table from asset ref + const FString& DataTablePath = InStructPropertyHandle->GetMetaData(TEXT("DataTable")); + if (UDataTable* DataTable = LoadObject(nullptr, *DataTablePath, nullptr)) + { + DataTablePropertyHandle->SetValue(DataTable); + } + else + { + UE_LOG(LogDataTable, Warning, TEXT("No Datatable found at %s"), *DataTablePath); + } + } + else + { + UE_LOG(LogDataTable, Warning, TEXT("No Datatable meta tag present on property %s"), *InStructPropertyHandle->GetPropertyDisplayName().ToString()); + } + + + + FPropertyComboBoxArgs ComboArgs(RowNamePropertyHandle, + FOnGetPropertyComboBoxStrings::CreateSP(this, &FStevesFixedDataTableCustomisationLayout::OnGetRowStrings), + FOnGetPropertyComboBoxValue::CreateSP(this, &FStevesFixedDataTableCustomisationLayout::OnGetRowValueString)); + ComboArgs.ShowSearchForItemCount = 1; + + + TSharedRef BrowseTableButton = PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP(this, &FStevesFixedDataTableCustomisationLayout::BrowseTableButtonClicked), + LOCTEXT("SsBrowseToDatatable", "Browse to DataTable in Content Browser")); + HeaderRow + .NameContent() + [ + InStructPropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + .MaxDesiredWidth(0.0f) // don't constrain the combo button width + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + PropertyCustomizationHelpers::MakePropertyComboBox(ComboArgs) + ] + +SHorizontalBox::Slot() + .Padding(2.0f) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .AutoWidth() + [ + BrowseTableButton + ] + ]; ; + + FDataTableEditorUtils::AddSearchForReferencesContextMenu(HeaderRow, FExecuteAction::CreateSP(this, &FStevesFixedDataTableCustomisationLayout::OnSearchForReferences)); +} + +void FStevesFixedDataTableCustomisationLayout::BrowseTableButtonClicked() +{ + if (DataTablePropertyHandle.IsValid()) + { + UObject* SourceDataTable = nullptr; + if (DataTablePropertyHandle->GetValue(SourceDataTable) == FPropertyAccess::Success) + { + TArray Assets; + Assets.Add(SourceDataTable); + GEditor->SyncBrowserToObjects(Assets); + } + } +} + +bool FStevesFixedDataTableCustomisationLayout::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(SourceDataTable); + + if (RowNamePropertyHandle->GetValue(OutName) == FPropertyAccess::Success) + { + return true; + } + } + } + return false; +} + +void FStevesFixedDataTableCustomisationLayout::OnSearchForReferences() +{ + UDataTable* DataTable; + FName RowName; + + if (GetCurrentValue(DataTable, RowName) && DataTable) + { + TArray AssetIdentifiers; + AssetIdentifiers.Add(FAssetIdentifier(DataTable, RowName)); + + FEditorDelegates::OnOpenReferenceViewer.Broadcast(AssetIdentifiers, FReferenceViewerParams()); + } +} + +FString FStevesFixedDataTableCustomisationLayout::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 FStevesFixedDataTableCustomisationLayout::OnGetRowStrings(TArray< TSharedPtr >& OutStrings, TArray>& OutToolTips, TArray& 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 AllRowNames; + if (DataTable != nullptr) + { + for (TMap::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(RowName.ToString())); + OutRestrictedItems.Add(false); + } +} + + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/StevesUEHelpersEd/Private/StevesFixedDataTableCustomisationLayout.h b/Source/StevesUEHelpersEd/Private/StevesFixedDataTableCustomisationLayout.h new file mode 100644 index 0000000..c138686 --- /dev/null +++ b/Source/StevesUEHelpersEd/Private/StevesFixedDataTableCustomisationLayout.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Containers/Array.h" +#include "Containers/UnrealString.h" +#include "IPropertyTypeCustomization.h" +#include "Templates/SharedPointer.h" +#include "UObject/NameTypes.h" + +class IPropertyHandle; +class SToolTip; +class UDataTable; +class UScriptStruct; +struct FAssetData; + +/// Drop-down for data table row name when the table itself is fixed in meta tags +class FStevesFixedDataTableCustomisationLayout : public IPropertyTypeCustomization +{ +public: + static TSharedRef MakeInstance() + { + return MakeShareable( new FStevesFixedDataTableCustomisationLayout ); + } + + virtual void CustomizeHeader(TSharedRef InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + // Not needed, but must be implemented + virtual void CustomizeChildren(TSharedRef InStructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override {} + +protected: + bool GetCurrentValue(UDataTable*& OutDataTable, FName& OutName) const; + void OnSearchForReferences(); + void OnGetRowStrings(TArray< TSharedPtr >& OutStrings, TArray>& OutToolTips, TArray& OutRestrictedItems) const; + FString OnGetRowValueString() const; + void BrowseTableButtonClicked(); + + TSharedPtr DataTablePropertyHandle; + TSharedPtr RowNamePropertyHandle; + +}; diff --git a/Source/StevesUEHelpersEd/Private/StevesUEHelpersEd.cpp b/Source/StevesUEHelpersEd/Private/StevesUEHelpersEd.cpp new file mode 100644 index 0000000..90661f0 --- /dev/null +++ b/Source/StevesUEHelpersEd/Private/StevesUEHelpersEd.cpp @@ -0,0 +1,20 @@ +#include "StevesUEHelpersEd.h" +#include "StevesFixedDataTableCustomisationLayout.h" + +#define LOCTEXT_NAMESPACE "FStevesUEHelpersEdModule" + +void FStevesUEHelpersEdModule::StartupModule() +{ + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomPropertyTypeLayout("StevesFixedDataTableRowHandle", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FStevesFixedDataTableCustomisationLayout::MakeInstance)); +} + +void FStevesUEHelpersEdModule::ShutdownModule() +{ + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.UnregisterCustomPropertyTypeLayout("StevesFixedDataTableRowHandle"); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FStevesUEHelpersEdModule, StevesUEHelpersEd) \ No newline at end of file diff --git a/Source/StevesUEHelpersEd/Public/StevesUEHelpersEd.h b/Source/StevesUEHelpersEd/Public/StevesUEHelpersEd.h new file mode 100644 index 0000000..eea7c5a --- /dev/null +++ b/Source/StevesUEHelpersEd/Public/StevesUEHelpersEd.h @@ -0,0 +1,11 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FStevesUEHelpersEdModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/StevesUEHelpersEd/StevesUEHelpersEd.Build.cs b/Source/StevesUEHelpersEd/StevesUEHelpersEd.Build.cs new file mode 100644 index 0000000..570ad75 --- /dev/null +++ b/Source/StevesUEHelpersEd/StevesUEHelpersEd.Build.cs @@ -0,0 +1,30 @@ +using UnrealBuildTool; + +public class StevesUEHelpersEd : ModuleRules +{ + public StevesUEHelpersEd(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + + "UnrealEd", + "PropertyEditor", + "DataTableEditor" + } + ); + } +} \ No newline at end of file diff --git a/StevesUEHelpers.uplugin b/StevesUEHelpers.uplugin index 9182728..7496227 100644 --- a/StevesUEHelpers.uplugin +++ b/StevesUEHelpers.uplugin @@ -1,35 +1,39 @@ { - "FileVersion" : 3, - "Version" : 1, - "VersionName" : "1.0", - "FriendlyName" : "Steve's UE4 Helpers", - "Description" : "A set of common helper classes for UE4 projects by Steve Streeing", - "Category" : "Code Utilities", - "CreatedBy" : "Steve Streeting", - "CreatedByURL" : "https://www.stevestreeting.com", - "DocsURL" : "", - "MarketplaceURL" : "", - "SupportURL" : "https://github.com/sinbad/StevesUEHelpers", - "EnabledByDefault" : true, - "CanContainContent" : false, - "IsBetaVersion" : true, - "Installed" : false, - "Modules" : - [ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Steve's UE4 Helpers", + "Description": "A set of common helper classes for UE4 projects by Steve Streeing", + "Category": "Code Utilities", + "CreatedBy": "Steve Streeting", + "CreatedByURL": "https://www.stevestreeting.com", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "https://github.com/sinbad/StevesUEHelpers", + "EnabledByDefault": true, + "CanContainContent": false, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ { - "Name" : "StevesUEHelpers", - "Type" : "Runtime", - "LoadingPhase" : "PreDefault" + "Name": "StevesUEHelpers", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "StevesUEHelpersEd", + "Type": "Editor", + "LoadingPhase": "PostDefault" } ], - "Plugins": [ + "Plugins": [ { "Name": "Paper2D", - "Enabled": true + "Enabled": true }, { "Name": "EnhancedInput", "Enabled": true } - ] + ] } \ No newline at end of file