// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Widgets/SWidget.h" #include "Layout/Margin.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "IPropertyTypeCustomization.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Views/SListView.h" #include "IDetailChildrenBuilder.h" #include "Engine/DataTable.h" #include "PropertyCustomizationHelpers.h" #include "IPropertyUtilities.h" #define LOCTEXT_NAMESPACE "FDataTableCustomizationLayout" /** * Customizes a DataTable asset to use a dropdown */ class FDataTableCustomizationLayout : public IPropertyTypeCustomization { public: static TSharedRef MakeInstance() { return MakeShareable( new FDataTableCustomizationLayout ); } /** IPropertyTypeCustomization interface */ virtual void CustomizeHeader( TSharedRef InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override { this->StructPropertyHandle = InStructPropertyHandle; if (StructPropertyHandle->HasMetaData(TEXT("RowType"))) { const FString& RowType = StructPropertyHandle->GetMetaData(TEXT("RowType")); RowTypeFilter = FName(*RowType); } HeaderRow .NameContent() [ InStructPropertyHandle->CreatePropertyNameWidget( FText::GetEmpty(), FText::GetEmpty(), false ) ]; } virtual void CustomizeChildren( TSharedRef InStructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils ) override { /** Get all the existing property handles */ DataTablePropertyHandle = InStructPropertyHandle->GetChildHandle( "DataTable" ); RowNamePropertyHandle = InStructPropertyHandle->GetChildHandle( "RowName" ); if( DataTablePropertyHandle->IsValidHandle() && RowNamePropertyHandle->IsValidHandle() ) { /** Queue up a refresh of the selected item, not safe to do from here */ StructCustomizationUtils.GetPropertyUtilities()->EnqueueDeferredAction(FSimpleDelegate::CreateSP(this, &FDataTableCustomizationLayout::OnDataTableChanged)); /** Setup Change callback */ FSimpleDelegate OnDataTableChangedDelegate = FSimpleDelegate::CreateSP( this, &FDataTableCustomizationLayout::OnDataTableChanged ); DataTablePropertyHandle->SetOnPropertyValueChanged( OnDataTableChangedDelegate ); /** Construct a asset picker widget with a custom filter */ StructBuilder.AddChildContent(LOCTEXT("DataTable_TableName", "Data Table")) .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) ]; /** Construct a combo box widget to select from a list of valid options */ StructBuilder.AddChildContent( LOCTEXT( "DataTable_RowName", "Row Name" ) ) .NameContent() [ SNew( STextBlock ) .Text( LOCTEXT( "DataTable_RowName", "Row Name" ) ) .Font( StructCustomizationUtils.GetRegularFont() ) ] .ValueContent() .MaxDesiredWidth(0.0f) // don't constrain the combo button width [ SAssignNew( RowNameComboButton, SComboButton ) .ToolTipText( this, &FDataTableCustomizationLayout::GetRowNameComboBoxContentText ) .OnGetMenuContent( this, &FDataTableCustomizationLayout::GetListContent ) .ContentPadding( FMargin( 2.0f, 2.0f ) ) .ButtonContent() [ SNew( STextBlock ) .Text( this, &FDataTableCustomizationLayout::GetRowNameComboBoxContentText ) ] ]; } } private: bool ShouldFilterAsset(const class FAssetData& AssetData) { if (!RowTypeFilter.IsNone()) { const UDataTable* DataTable = Cast(AssetData.GetAsset()); if (DataTable->RowStruct && DataTable->RowStruct->GetFName() == RowTypeFilter) { return false; } return true; } return false; } /** Init the contents the combobox sources its data off */ TSharedPtr InitWidgetContent() { TSharedPtr InitialValue = MakeShareable( new FString( LOCTEXT( "DataTable_None", "None" ).ToString() ) );; FName RowName; const FPropertyAccess::Result RowResult = RowNamePropertyHandle->GetValue( RowName ); RowNames.Empty(); /** Get the properties we wish to work with */ UDataTable* DataTable = NULL; DataTablePropertyHandle->GetValue( ( UObject*& )DataTable ); if( DataTable != NULL ) { /** Extract all the row names from the RowMap */ for( TMap::TConstIterator Iterator( DataTable->RowMap ); Iterator; ++Iterator ) { /** Create a simple array of the row names */ TSharedRef RowNameItem = MakeShareable( new FString( Iterator.Key().ToString() ) ); RowNames.Add( RowNameItem ); /** Set the initial value to the currently selected item */ if( Iterator.Key() == RowName ) { InitialValue = RowNameItem; } } } /** Reset the initial value to ensure a valid entry is set */ if ( RowResult != FPropertyAccess::MultipleValues ) { FName NewValue = FName( **InitialValue ); RowNamePropertyHandle->SetValue( NewValue ); } return InitialValue; } /** Returns the ListView for the ComboButton */ TSharedRef GetListContent(); /** Delegate to refresh the drop down when the datatable changes */ void OnDataTableChanged() { CurrentSelectedItem = InitWidgetContent(); if( RowNameComboListView.IsValid() ) { RowNameComboListView->SetSelection(CurrentSelectedItem); RowNameComboListView->RequestListRefresh(); } } /** Return the representation of the the row names to display */ TSharedRef HandleRowNameComboBoxGenarateWidget( TSharedPtr InItem, const TSharedRef& OwnerTable ) { return SNew( STableRow>, OwnerTable) [ SNew( STextBlock ).Text( FText::FromString(*InItem) ) ]; } /** Display the current selection */ FText GetRowNameComboBoxContentText( ) const { FString RowNameValue; const FPropertyAccess::Result RowResult = RowNamePropertyHandle->GetValue( RowNameValue ); if ( RowResult != FPropertyAccess::MultipleValues ) { TSharedPtr SelectedRowName = CurrentSelectedItem; if ( SelectedRowName.IsValid() ) { return FText::FromString(*SelectedRowName); } else { return LOCTEXT("DataTable_None", "None"); } } return LOCTEXT( "MultipleValues", "Multiple Values" ); } /** Update the root data on a change of selection */ void OnSelectionChanged( TSharedPtr SelectedItem, ESelectInfo::Type SelectInfo ) { if( SelectedItem.IsValid() ) { CurrentSelectedItem = SelectedItem; FName NewValue = FName( **SelectedItem ); RowNamePropertyHandle->SetValue( NewValue ); // Close the combo RowNameComboButton->SetIsOpen( false ); } } /** Called by Slate when the filter box changes text. */ void OnFilterTextChanged( const FText& InFilterText ) { FString CurrentFilterText = InFilterText.ToString(); FName RowName; const FPropertyAccess::Result RowResult = RowNamePropertyHandle->GetValue( RowName ); RowNames.Empty(); /** Get the properties we wish to work with */ UDataTable* DataTable = NULL; DataTablePropertyHandle->GetValue( ( UObject*& )DataTable ); if( DataTable != NULL ) { /** Extract all the row names from the RowMap */ for( TMap::TConstIterator Iterator( DataTable->RowMap ); Iterator; ++Iterator ) { /** Create a simple array of the row names */ FString RowString = Iterator.Key().ToString(); if( CurrentFilterText == TEXT("") || RowString.Contains(CurrentFilterText) ) { TSharedRef RowNameItem = MakeShareable( new FString( RowString ) ); RowNames.Add( RowNameItem ); } } } RowNameComboListView->RequestListRefresh(); } /** The comboButton objects */ TSharedPtr RowNameComboButton; TSharedPtr > > RowNameComboListView; TSharedPtr CurrentSelectedItem; /** Handle to the struct properties being customized */ TSharedPtr StructPropertyHandle; TSharedPtr DataTablePropertyHandle; TSharedPtr RowNamePropertyHandle; /** A cached copy of strings to populate the combo box */ TArray > RowNames; /** The MetaData derived filter for the row type */ FName RowTypeFilter; }; #undef LOCTEXT_NAMESPACE